LDAP PermGen内存泄漏
每当我在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 Web应用程序管理器中的重新加载然后查找泄漏时,Tomcat报告存在泄漏并且分析器证实了它。
在这个例子中泄漏几乎没有引人注意,但是它会在大型Web应用程序中导致OutOfMemory。 我没有发现任何开放的JDK漏洞。
更新1
我试图使用Jetty 9.2而不是Tomcat,我仍然看到泄漏,所以这不是Tomcat的错。 要么是JDK错误,要么我做错了什么。
更新2
尽管我的示例演示了泄漏,但它没有显示内存不足错误,因为它具有非常小的PermGen覆盖区。 我创建了另一个应该能够重现OutOfMemoryError的分支。 我只是将Spring,Hibernate和Logback依赖项添加到该项目中以增加PermGen消耗。 这些依赖关系与泄漏无关,我可以使用其他任何其他方法。 这些的唯一目的是使PermGen的消耗足够大,以便能够获得OutOfMemoryError。
重现OutOfMemoryError的步骤:
下载或复制outofmemory-demo分支。
确保你有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,打开项目的源文件srcmainjavaorgexampleLDAPLeakDemo.java
,删除useLDAP();
调用并保存它。
重复步骤5-8,只有这一次没有OutOfMemoryError,因为LDAP代码永远不会被调用。
首先:是的,Sun / Oracle提供的LDAP API可能触发ClassLoader泄漏。 它位于已知违规者的列表中,因为如果系统属性com.sun.jndi.ldap.connect.pool.timeout
> 0, com.sun.jndi.ldap.LdapPoolManager
将在Web应用程序中产生一个新的线程,该线程首先调用LDAP。
话虽如此,我在我的ClassLoader泄漏预防库中添加了您的示例代码作为测试用例,这样我就可以获得泄漏的自动堆转储。 根据我的分析,实际上代码中没有任何泄漏,但是似乎需要多个垃圾收集器周期才能获得GC问题中的ClassLoader:ed(可能是由于瞬态引用 - 并没有挖掘到这一点)许多)。 这可能会诱使Tomcat相信有泄漏,即使没有泄漏。
然而,既然你说你最终会得到一个OutOfMemoryError
,不是我错了,或者你的应用中有其他东西导致这些泄漏。 如果您将我的ClassLoader泄漏防护库添加到您的应用程序,它是否仍然泄漏/导致OOME
? Preventor会记录任何警告吗?
如果您设置您的应用程序服务器以在存在OOME
时创建堆转储,则可以使用Eclipse Memory Analyzer查找泄漏。 我在这里详细解释了这个过程。
我发布这个问题已经有一段时间了。 我终于找到真正发生的事情,所以我认为我把它作为答案发布,以防@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
后出现这种情况的LDAP初始化期间new InitialDirContext(env)
从我的问题在代码调用。 问题是java.lang.Throwable
,它是所有异常(包括javax.naming.directory.SchemaViolationException
)的超类,具有backtrace
字段。 该字段包含调用构造函数时栈引用中所有类的引用,包括我自己的org.example.LDAPLeakDemo
类,该类继而保存对Web应用程序类加载器的引用。
以下是Java 9中修复的类似漏洞https://bugs.openjdk.java.net/browse/JDK-8146961
链接地址: http://www.djcxy.com/p/54999.html下一篇: java.lang.OutOfMemoryError: PermGen space error with Jetty