FULL PRODUCT VERSION :
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]
A DESCRIPTION OF THE PROBLEM :
The PoolCleaner thread started by the LdapPoolManager when the idle timeout is positive does not explicitly set the context class loader so it inherits the current context class loader. If the current context class loader when this code is triggered happens to be one that is meant to be disposable (e.g. a web application class loader) the class loader will be pinned in memory until the Thread stops. This prevents the class loader from being GC'd when no longer required and triggers a memory leak.
The fix looks to be trivial. Something along the lines of the following around line 45 of PoolCleaner:
setContextClassLoader(PoolCleaner.class.getClassLoader());
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the demonstration app:
https://github.com/markt-asf/memory-leaks/blob/master/src/org/apache/markt/leaks/ldap/PoolManagerLeak.java
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expected result (on stdout) is:
One call to GC was required, as expected.
No leak
ACTUAL -
The actual result (on stdout) is:
There were 3 calls to GC. Ideally, only one should be required.
Leak
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
https://github.com/markt-asf/memory-leaks/blob/master/src/org/apache/markt/leaks/ldap/PoolManagerLeak.java
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
The work-around is to call
Class.forName("com.sun.jndi.ldap.LdapPoolManager");
when the context class loader is a class loader than is not expected to become eligable for GC during the lifetime of the JVM.