首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >LDAP PermGen内存泄漏

LDAP PermGen内存泄漏
EN

Stack Overflow用户
提问于 2015-09-28 16:50:26
回答 2查看 1.2K关注 0票数 2

每当我在web应用程序中使用LDAP时,它都会导致类加载器泄漏,奇怪的是分析器找不到任何GC根。

我创建了一个简单的web应用程序来演示泄漏,它只包括这个类:

代码语言:javascript
复制
@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的步骤:

  1. 下载或克隆外置存储器-演示分支
  2. 确保您有JDK 7以及Tomcat和Maven的任何版本(我使用了最新版本-- JDK 1.7.0_79和Tomcat8.0.26)。
  3. 减小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)。
  4. 转到Tomcat的conf目录,打开tomcat-users.xml,并在<role rolename="manager-gui"/><user username="admin" password="1" roles="manager-gui"/>中添加<tomcat-users></ tomcat-users>,以便能够使用。
  5. 转到项目的目录并使用mvn package构建一个.war。
  6. 转到Tomcat的webapp目录,删除除管理器目录之外的所有内容,然后在这里复制.war。
  7. 运行Tomcat的启动脚本(bin\startup.bat或bin/startup.sh)并打开http://localhost:8080/manager/,使用用户名admin和密码1。
  8. 单击Reload,您将在Tomcat的控制台中看到java.lang.OutOfMemoryError: PermGen空间。
  9. 停止Tomcat,打开项目的源文件src\main\java\org\example\LDAPLeakDemo.java,删除useLDAP();调用并保存它。
  10. 重复步骤5-8,只是这次没有OutOfMemoryError,因为LDAP代码从未被调用过。
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-05-02 23:26:00

我好久没发这个问题了。我终于找到了真正发生的事情,所以我想我把它作为答案,以防@MattiasJiderhamn或其他人感兴趣。

分析器找不到任何GC根的原因是JVM隐藏了java.lang.Throwable.backtrace字段,如https://bugs.openjdk.java.net/browse/JDK-8158237中所描述的那样。既然这个限制已经消失,我就可以获得GC根目录了:

代码语言:javascript
复制
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类有一个静态字段

代码语言:javascript
复制
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中一个类似的漏洞

票数 1
EN

Stack Overflow用户

发布于 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查找泄漏。我已经详细地解释了这个过程,这里

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32828037

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档