Whenever I use LDAP in a web application it causes a classloader leak, and the strange thing is profilers don’t find any GC roots.
I’ve created a simple web application that demonstrates the leak, it only includes this class:
@WebListener
public class LDAPLeakDemo implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
useLDAP();
}
public void contextDestroyed(ServletContextEvent sce) {}
private void useLDAP() {
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://ldap.forumsys.com:389");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=read-only-admin,dc=example,dc=com");
env.put(Context.SECURITY_CREDENTIALS, "password");
try {
DirContext ctx = null;
try {
ctx = new InitialDirContext(env);
System.out.println("Created the initial context");
} finally {
if (ctx != null) {
ctx.close();
System.out.println("Closed the context");
}
}
} catch (NamingException e) {
e.printStackTrace();
}
}
}
The source code is available here. I’m using a public LDAP test server for this example, so it should work for everyone if you want to try it. I tried it with the latest JDK 7 and 8 and Tomcat 7 and 8 with the same result – when I click on Reload in Tomcat Web Application Manager and then on Find leaks, Tomcat reports that there’s a leak and profilers confirm it.
The leak is barely noticeable in this example, but it causes OutOfMemory in a big web application. I didn’t find any open JDK bugs about it.
UPDATE 1
I've tried to use Jetty 9.2 instead of Tomcat and I still see the leak, so it's not Tomcat's fault. Either it's a JDK bug or I'm doing something wrong.
UPDATE 2
Even though my example demonstrates the leak, it doesn’t demonstrate the out of memory error, because it has very small PermGen footprint. I’ve created another branch that should be able to reproduce OutOfMemoryError. I just added Spring, Hibernate and Logback dependencies to the project to increase PermGen consumption. These dependencies have nothing to do with the leak and I could have used any others instead. The only purpose of those is to make PermGen consumption big enough to be able to get OutOfMemoryError.
Steps to reproduce OutOfMemoryError:
Download or clone the outofmemory-demo branch.
Make sure you have JDK 7 and any version of Tomcat and Maven (I used the latest versions - JDK 1.7.0_79 and Tomcat 8.0.26).
Decrease PermGen size to be able to see the error after the first reload. Create setenv.bat (Windows) or setenv.sh (Linux) in Tomcat’s bin directory and add
set "JAVA_OPTS=-XX:PermSize=24m -XX:MaxPermSize=24m"
(Windows) orexport "JAVA_OPTS=-XX:PermSize=24m -XX:MaxPermSize=24m"
(Linux).Go to Tomcat’s conf directory, open tomcat-users.xml and add
<role rolename="manager-gui"/><user username="admin" password="1" roles="manager-gui"/>
inside<tomcat-users></ tomcat-users>
to be able to use Tomcat Web Application Manager.Go to project’s directory and use
mvn package
to build a .war.Go to Tomcat’s webapps directory, delete everything except the manager directory and copy the .war here.
Run Tomcat’s start script (bin\startup.bat or bin/startup.sh) and open http://localhost:8080/manager/, use username admin and password 1.
Click on Reload and you should see java.lang.OutOfMemoryError: PermGen space in Tomcat's console.
Stop Tomcat, open project’s source file
src\main\java\org\example\LDAPLeakDemo.java
, remove theuseLDAP();
call and save it.Repeat steps 5-8, only this time there’s no OutOfMemoryError, because the LDAP code is never called.