每当我在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();
}
}
}源代码是可用的这里。我在这个例子中使用了公共LDAP测试服务器,所以如果您想尝试它,它应该适用于每个人。我用最新的JDK 7和8以及Tomcat 7和8进行了尝试,结果相同--当我单击Tomcat应用程序管理器中的Reload,然后在查找泄漏时,Tomcat报告说有泄漏,分析器确认了它。
在本例中,这个漏洞几乎不明显,但它会导致大型web应用程序中的OutOfMemory。我没有发现任何公开的JDK bug。
更新1
我尝试使用Jetty9.2而不是Tomcat,我仍然看到漏洞,所以这不是Tomcat的错。要么是JDK错误,要么是我做错了什么。
更新2
尽管我的示例演示了泄漏,但它没有演示内存不足错误,因为它的PermGen占用空间非常小。我已经创建了能够复制另一个分支的OutOfMemoryError。我只是在项目中添加了Spring、Hibernate和Logback依赖项,以增加PermGen消耗。这些依赖项与泄漏无关,我可以使用其他任何依赖项。这样做的唯一目的是使PermGen的消费量足够大,以便能够获得OutOfMemoryError。
复制OutOfMemoryError的步骤:
set "JAVA_OPTS=-XX:PermSize=24m -XX:MaxPermSize=24m" (Windows)或export "JAVA_OPTS=-XX:PermSize=24m -XX:MaxPermSize=24m" (Linux)。<role rolename="manager-gui"/><user username="admin" password="1" roles="manager-gui"/>中添加<tomcat-users></ tomcat-users>,以便能够使用。mvn package构建一个.war。src\main\java\org\example\LDAPLeakDemo.java,删除useLDAP();调用并保存它。发布于 2018-05-02 23:26:00
我好久没发这个问题了。我终于找到了真正发生的事情,所以我想我把它作为答案,以防@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包含在readOnlyEx初始化期间分配给javax.naming.directory.SchemaViolationException实例的readOnlyEx字段,该初始化发生在我的问题中的代码中的new InitialDirContext(env)调用之后。问题是java.lang.Throwable是包括javax.naming.directory.SchemaViolationException在内的所有异常的超类,它有backtrace字段。此字段包含在调用构造函数时对堆栈跟踪中所有类的引用,包括我自己的org.example.LDAPLeakDemo类,后者反过来包含对web应用程序类加载器的引用。
以下是Java9 https://bugs.openjdk.java.net/browse/JDK-8146961中一个类似的漏洞
发布于 2015-09-29 05:56:11
首先:是的,Sun/Oracle提供的LDAP API可以触发ClassLoader泄漏。它在我的已知罪犯名单上,因为如果系统属性com.sun.jndi.ldap.connect.pool.timeout >0,com.sun.jndi.ldap.LdapPoolManager将在首次调用LDAP的web应用程序中生成一个新线程。
尽管如此,我在我的ClassLoader防漏库中添加了示例代码作为测试用例,这样我就可以获得泄漏的自动堆转储。根据我的分析,实际上您的代码中没有泄漏,但是似乎需要一个以上的垃圾收集器循环才能获得ClassLoader中的GC:ed (可能是由于短暂的引用--还没有深入研究它)。这可能会使Tomcat相信存在漏洞,即使没有泄漏。
然而,既然你说你最终得到了一个OutOfMemoryError,要么我错了,要么你的应用程序中有其他东西导致了这些泄漏。如果将我的ClassLoader防漏库添加到应用程序中,它是否仍会泄漏/导致OOME?Preventor记录任何警告吗?
如果您将应用服务器设置为在有OOME时创建堆转储,则可以使用Eclipse查找泄漏。我已经详细地解释了这个过程,这里。
https://stackoverflow.com/questions/32828037
复制相似问题