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

    上一篇: LDAP PermGen memory leak

    下一篇: java.lang.OutOfMemoryError: PermGen space error with Jetty