Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading a KeyStore in Java 7 leaks the Classloader

When loading a KeyStore in Java 7 the Classloader is leaked.

I have confirmed this using the "Find Leaks" feature in Tomcat 7.0.47 and classloader-leak-prevention. Here is the test code, the webapp with the leak in @Configuration and the webapp with the leak in @Controller.

Essentially these lines cause the leak for me:

InputStream is = null;
try {
    is = new FileInputStream("./app.truststore");
    KeyStore keyStore = KeyStore.getInstance("JKS");
    keyStore.load(is, "changeit".toCharArray());
} catch (Exception e) {
    System.out.println(e);
} finally {
    if (is != null) {
        is.close();
    }
}

If I remove KeyStore.load() everything works fine but that is obviously not a functioning solution.

It does not work on Oracle JDK 1.7u15, u17, u21, u25, u40 and u45 as well as OpenJDK 1.7u40 and u45.

It works on Oracle JDK 1.6u39, u41, u43 and 45 as well as OpenJDK 1.6.0.

This was tested on Microsoft Windows Server 2008 R2 Standard 64 Bit. The OpenJDKs are the latest unofficial builds by alexkasko on GitHub.

Does anyone have an idea what may be causing the Classloader leak? I tried using a heap dump and calling "shortest path to GC root" but that returned no results.

like image 862
Arlo Avatar asked Nov 26 '13 10:11

Arlo


2 Answers

The Classloader does not leak. When deploying a bunch of empty applications and PermGen reaches a certain threshold Tomcat's Find Leaks stops reporting the application. Thus it was a false positive.

like image 74
Arlo Avatar answered Nov 15 '22 04:11

Arlo


I revisited this with a couple of different approaches. I took your logic and threw it in a webapp. The first one I tried used Spring MVC with Gemfire and I added your code to the web app initializer. When testing that code against an eval of a commercial tool called Plumbr it detected a leaking classloader. However I was unable to see the report in the eval.

The Plumbr people contacted me and provided me with the report and I could see that Gemfire appeared to be the root of the issue. I was using Gemfire sessions and remote caching, every now and then it doesn't shut down gracefully and in this case appeared as a leak.

I then trimmed everything back to the simplest webapp, 1 spring controller and 1 bean with the logic gleaned from your code as follows:

public class ClassloaderLeakingBean {

    private static Logger logger = LoggerFactory.getLogger(ClassloaderLeakingBean.class);
    public void initiateLeak(String trustStore) throws Exception {
        InputStream is = null;
        try {
            is = new FileInputStream(trustStore);
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(is, "password".toCharArray());
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (is != null) {
                is.close();
                logger.info("The Input Stream is Closed!");
            }
        }

    }
}

I then threw the bean into a controller that simply called the initiateLeak method in response to a GET request. This simplified version showed no signs of a classloader leak no matter what I throw at it.

The java runtime version is as follows:

java version "1.7.0" Java(TM) SE Runtime Environment (build 1.7.0-b147) Java HotSpot(TM) 64-Bit Server VM (build 21.0-b17, mixed mode)

I compiled the code using maven with a source and target of 1.7.

I am going to investigate this further as a standalone app but I haven't seen any reports of classloader leaks against Java 7 and the KeyStore class.

Things to keep in mind: I am running on 64bit JVM, Windows 7 Enterprise, which could act differently than a 32 bit environment. I would personally stay away from unofficial builds when looking for memory leaks.

like image 37
TechTrip Avatar answered Nov 15 '22 02:11

TechTrip