【发布时间】:2015-12-25 23:49:57
【问题描述】:
每当我在 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,在
<tomcat-users></ tomcat-users>中添加<role rolename="manager-gui"/><user username="admin" password="1" roles="manager-gui"/>,即可使用Tomcat Web Application Manager。进入项目目录并使用
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 space。
停止Tomcat,打开项目源文件
src\main\java\org\example\LDAPLeakDemo.java,删除useLDAP();调用并保存。重复步骤 5-8,只是这次没有 OutOfMemoryError,因为从未调用过 LDAP 代码。
【问题讨论】:
-
Tomcat 究竟报告了什么?
-
@EJP 它显示了它的标准泄漏消息——“以下 Web 应用程序已停止(重新加载、取消部署),但它们之前运行的类仍加载到内存中,从而导致内存泄漏(使用探查器确认)”。
-
那是因为之前的条件。你需要向我们展示这一点。从长远来看,将类保留在内存中并不是任何类型的内存泄漏。这既正常又必不可少。你不断增长的记忆问题出在其他地方。
-
@EJP 你在说什么先决条件?此示例项目仅包含我的问题中的类。您可以从我提供的链接下载该项目并复制它。我知道垃圾收集器并不总是收集旧的类加载器,但如果 PermGen 中没有足够的空间,它必须收集它以避免内存不足错误。否则它会因为泄漏而无法收集。
标签: java memory-leaks ldap classloader permgen