在 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 中重新部署应用程序时发生内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

Tomcat 7 中的内存泄漏 Grails 应用程序

内存泄漏 - Tomcat、Spring MVC

Tomcat 8 内存泄漏

重新部署时 Google Cloud Pub Sub 内存泄漏(基于 Netty)

Tomcat如何检测内存泄漏

tomcat内存泄漏存入dump文件