每当我在Web应用程序中使用LDAP时,都会导致类加载器泄漏,奇怪的是分析器找不到任何GC根。
我创建了一个简单的Web应用程序来演示泄漏,它只包括这个类:
@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();
}
}
}
源代码可用here。我在这个例子中使用a public LDAP test server,所以如果你想尝试它,它应该适用于所有人。 我尝试使用最新的JDK 7和8以及Tomcat 7和8,结果相同 - 当我在Tomcat Web应用程序管理器中单击重新加载然后在查找泄漏时,Tomcat报告存在泄漏,并且分析器确认它。
在此示例中几乎没有发现泄漏,但它会在大型Web应用程序中导致OutOfMemory。我没有发现任何关于它的JDK错误。
更新1
我尝试使用Jetty 9.2而不是Tomcat,我仍然看到了泄漏,所以这不是Tomcat的错。要么是JDK错误,要么我做错了。
更新2
尽管我的示例演示了泄漏,但它并未演示内存不足错误,因为它的PermGen占用空间非常小。我创建了another branch,它应该能够重现OutOfMemoryError。我刚刚将Spring,Hibernate和Logback依赖项添加到项目中以增加PermGen的消耗。这些依赖关系与泄漏无关,我可以使用任何其他依赖。这些的唯一目的是使PermGen消耗足够大,以便能够获得OutOfMemoryError。
重现OutOfMemoryError的步骤:
下载或克隆outofmemory-demo branch。
确保您拥有JDK 7以及任何版本的Tomcat和Maven(我使用的是最新版本 - JDK 1.7.0_79和Tomcat 8.0.26)。
降低PermGen大小,以便在第一次重新加载后能够看到错误。在Tomcat的bin目录中创建setenv.bat(Windows)或setenv.sh(Linux)并添加set "JAVA_OPTS=-XX:PermSize=24m -XX:MaxPermSize=24m"
(Windows)或export "JAVA_OPTS=-XX:PermSize=24m -XX:MaxPermSize=24m"
(Linux)。
转到Tomcat的conf目录,打开tomcat-users.xml并在<role rolename="manager-gui"/><user username="admin" password="1" roles="manager-gui"/>
中添加<tomcat-users></ tomcat-users>
以便能够使用Tomcat Web应用程序管理器。
转到项目目录并使用mvn package
构建.war。
转到Tomcat的webapps目录,删除除manager目录以外的所有内容,并在此处复制.war。
运行Tomcat的启动脚本(bin \ startup.bat或bin / startup.sh)并打开http://localhost:8080/manager/,使用用户名admin和密码1.
点击Reload,你会在Tomcat的控制台中看到java.lang.OutOfMemoryError:PermGen空间。
停止Tomcat,打开项目的源文件src\main\java\org\example\LDAPLeakDemo.java
,删除useLDAP();
来电并保存。
重复步骤5-8,只是这次没有OutOfMemoryError,因为从不调用LDAP代码。
答案 0 :(得分:1)
首先:是的,Sun / Oracle提供的LDAP API可以触发ClassLoader泄漏。它在my list of known offenders上,因为如果系统属性com.sun.jndi.ldap.LdapPoolManager
是&gt; 0 OutOfMemoryError
将生成在首次调用LDAP的Web应用程序中运行的新线程。
话虽这么说,我在你的ClassLoader Leak Prevention library中添加了你的示例代码作为测试用例,这样我就可以获得泄漏的自动堆转储。根据我的分析,你的代码实际上没有泄漏,但是它似乎需要多个垃圾收集器周期来获得有问题的ClassLoader GC:ed(可能是由于瞬态引用 - 没有挖到它许多)。这可能会诱使Tomcat相信存在泄漏,即使没有泄漏。
然而,因为你说你最终得到OOME
,要么我错了,要么你的应用中还有其他东西导致这些泄漏。如果您将my ClassLoader Leak Prevention library添加到您的应用中,它是否仍会泄漏/导致OOME
?预防者是否记录任何警告?
如果设置应用程序服务器以在存在IS NULL
时创建堆转储,则可以使用Eclipse Memory Analyzer查找泄漏。我已经详细解释了这个过程here。
答案 1 :(得分:0)
我发布此问题已经有一段时间了。我终于找到了真正发生的事情,所以我认为我将其作为答案发布,以防@MattiasJiderhamn或其他人感兴趣。
分析器找不到任何GC根的原因是因为JVM隐藏java.lang.Throwable.backtrace
字段,如https://bugs.openjdk.java.net/browse/JDK-8158237中所述。既然这个限制已经消失了,我就可以获得GC根目录了:
this - value: org.apache.catalina.loader.WebappClassLoader #2
<- <classLoader> - class: org.example.LDAPLeakDemo, value: org.apache.catalina.loader.WebappClassLoader #2
<- [10] - class: java.lang.Object[], value: org.example.LDAPLeakDemo class LDAPLeakDemo
<- [2] - class: java.lang.Object[], value: java.lang.Object[] #3394
<- backtrace - class: javax.naming.directory.SchemaViolationException, value: java.lang.Object[] #3386
<- readOnlyEx - class: com.sun.jndi.toolkit.dir.HierMemDirCtx, value: javax.naming.directory.SchemaViolationException #1
<- EMPTY_SCHEMA (sticky class) - class: com.sun.jndi.ldap.LdapCtx, value: com.sun.jndi.toolkit.dir.HierMemDirCtx #1
此泄漏的原因是JDK中的LDAP实现。 com.sun.jndi.ldap.LdapCtx
类有静态字段
private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();
com.sun.jndi.toolkit.dir.HierMemDirCtx
包含在LDAP初始化期间分配给readOnlyEx
实例的javax.naming.directory.SchemaViolationException
字段,该字段在我的问题代码中new InitialDirContext(env)
调用之后发生。问题是java.lang.Throwable
,它是所有例外的超类,包括javax.naming.directory.SchemaViolationException
,具有backtrace
字段。该字段包含对构造函数被调用时stacktrace中所有类的引用,包括我自己的org.example.LDAPLeakDemo
类,后者依次保存对Web应用程序类加载器的引用。