在 Tomcat 中重新部署应用程序时发生内存泄漏
Posted
技术标签:
【中文标题】在 Tomcat 中重新部署应用程序时发生内存泄漏【英文标题】:Memory leak when redeploying application in Tomcat 【发布时间】:2011-12-08 22:50:36 【问题描述】:当我在 tomcat 中重新部署我的应用程序时,我遇到了以下问题:
The web application [] created a ThreadLocal with key of type
[java.lang.ThreadLocal] (value [java.lang.ThreadLocal@10d16b])
and a value of type [com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty]
(value [com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty@1a183d2]) but
failed to remove it when the web application was stopped.
This is very likely to create a memory leak.
另外,我在我的应用程序中使用 ehcache。这似乎也导致了以下异常。
SEVERE: The web application [] created a ThreadLocal with key of type [null]
(value [com.sun.xml.bind.v2.ClassFactory$1@24cdc7]) and a value of type [java
.util.WeakHashMap...
ehcache 似乎创建了一个弱哈希映射,我收到消息说这很可能会造成内存泄漏。
我在网上搜索了一下,发现了这个, http://jira.pentaho.com/browse/PRD-3616 但我无权访问服务器。
请让我知道这些警告是否对功能有任何影响,或者可以忽略它们吗?我在 tomcat 管理器中使用了“查找内存泄漏”选项,它显示“未发现内存泄漏”
【问题讨论】:
警告意味着您在不重新启动 Tomcat 的情况下重新部署应用程序的能力受到限制。 Webapps 长期以来一直受到这种类型的内存泄漏的困扰。除非您重新部署应用程序,否则它们不会产生任何影响。我不知道,但我怀疑 Tomcat 输出中的这些消息(它们会在一两年内再次出现)对框架构建者施加压力,要求它们在重新启动后开始正确清理。 【参考方案1】:当您重新部署应用程序时,Tomcat 会创建一个新的类加载器。旧的类加载器必须进行垃圾回收,否则会出现 permgen 内存泄漏。
Tomcat 无法检查垃圾收集是否有效,但它知道几个常见的故障点。如果 webapp 类加载器将 ThreadLocal
设置为一个实例,该实例的类由 webapp 类加载器本身加载,则 servlet 线程持有对该实例的引用。这意味着类加载器不会被垃圾回收。
Tomcat 会进行许多此类检测,请参阅here for more information。清理线程局部变量很困难,您必须在访问的每个线程中的ThreadLocal
上调用remove()
。实际上,这仅在您多次重新部署 Web 应用程序的开发过程中很重要。在生产中,您可能不会重新部署,因此可以忽略。
要真正找出哪些实例定义了线程局部变量,您必须使用分析器。例如JProfiler 中的堆遍历器(免责声明:我的公司开发 JProfiler)将帮助您找到那些线程本地。选择报告的值类(com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty 或 com.sun.xml.bind.v2.ClassFactory)并显示累积的传入引用。其中之一将是java.lang.ThreadLocal$ThreadLocalMap$Entry
。选择该传入引用类型的引用对象并切换到分配视图。您将看到实例已分配的位置。有了这些信息,您就可以决定是否可以对此采取措施。
【讨论】:
如我所见,JProfiler 似乎不是免费的开源解决方案。你能推荐一些其他的选择吗 @Raghav Eclipse 内存分析器也非常适合分析堆转储。 eclipse.org/mat【参考方案2】:Mattias Jiderhamn 拥有出色的 6-part article 非常清楚地解释了有关类加载器泄漏的理论和实践。更好的是,他还发布了一个 jar 文件,我们可以将其包含在我们的 war 文件中。我在我的网络应用程序上尝试过,jar 文件就像一个魅力! jar 文件称为 classloader-leak-prevention.jar。使用它就像将它添加到我们的 web.xml 一样简单
<listener>
<listener-class>se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor</listener-class>
</listener>
然后将其添加到我们的 pom.xml 中
<dependency>
<groupId>se.jiderhamn</groupId>
<artifactId>classloader-leak-prevention</artifactId>
<version>1.15.2</version>
</dependency>
有关详细信息,请参阅 project home page hosted on GitHub 要么 Part 6 of his article
【讨论】:
【参考方案3】:创建线程而不正确清理它们最终会耗尽您的内存 - 去过那里,做到了。
那些仍然想知道快速解决方案/解决方法的人,可以去以下:
如果运行独立的 tomcat,请终止 javaw.exe 或承载它的进程。 如果从 Eclipse 运行,请终止 eclipse.exe 和 java.exe 或封闭进程。 仍未解决,检查任务管理器,可能导致此问题的进程将显示为最高内存 用法 - 做你的分析并杀死它。
您应该很好地重新部署这些东西并继续进行,而不会出现内存问题。
【讨论】:
【参考方案4】:我猜你可能已经看到了,但以防 ehcache 文档向 put the lib in tomcat and not in WEB-INF/lib 推荐。
【讨论】:
【参考方案5】:我建议在 ServletRequestListener 中初始化 thread locals。
ServletRequestListener
有两种方法:一种用于初始化,一种用于销毁。
这样,您可以清理您的ThreadLocal
。示例:
public class ContextInitiator implements ServletRequestListener
@Override
public void requestInitialized(ServletRequestEvent sre)
context = new ThreadLocal<ContextThreadLocal>()
@Override
protected ContextThreadLocal initialValue()
ContextThreadLocal context = new ContextThreadLocal();
return context;
;
context.get().setRequest(sre.getServletRequest());
@Override
public void requestDestroyed(ServletRequestEvent sre)
context.remove();
web.xml
:
<listener>
<listener-class>ContextInitiator</listener-class>
</listener>
【讨论】:
以上是关于在 Tomcat 中重新部署应用程序时发生内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章