在Tomcat中重新部署应用程序时发生内存泄漏

47

当我在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似乎创建了一个弱哈希映射表,而我收到了这样的消息,很可能会导致内存泄漏。

我在网上搜索发现了这个链接,但我没有访问服务器的权限。

请告诉我这些警告是否有功能影响或者可以忽略?我在tomcat管理器中使用了“查找内存泄漏”选项,它显示“未发现内存泄漏”。


2
警告意味着您重新部署应用程序而无需重新启动Tomcat本身的能力受到限制。Web应用程序长期以来一直受到这种类型的内存泄漏的困扰。除非您重新部署应用程序,否则它们不会产生影响。我不知道,但我怀疑Tomcat输出中的这些消息,这些消息在一两年前开始出现,是为了向框架构建者施加压力,要求他们在重新启动后开始适当地清理自己。 - Ryan Stewart
5个回答

43

当您重新部署应用程序时,Tomcat将创建一个新的类加载器。旧的类加载器必须被垃圾回收,否则您会遇到PermGen内存泄漏。

Tomcat不能检查垃圾回收是否有效,但它知道几个常见故障点。如果Web应用程序类加载器使用由其自己加载的类的实例设置了ThreadLocal,则Servlet线程将持有对该实例的引用。这意味着类加载器将不会被垃圾回收。

Tomcat执行了许多此类检测,请参见此处以获取更多信息。清除线程本地变量很困难,您需要在每个已访问它的线程上调用remove()。实际上,在开发期间多次重新部署Web应用程序时才特别重要。在生产环境中,您可能不会重新部署,因此可以忽略此问题。

要真正找出哪些实例定义了线程本地变量,您必须使用分析器。例如,JProfiler中的堆游走器(免责声明:我的公司开发JProfiler)将帮助您找到这些线程本地变量。选择报告的值类(com.sun.xml.bind.v2.runtime.property.SingleElementLeafProperty或com.sun.xml.bind.v2.ClassFactory),并显示累积的传入引用。其中之一将是java.lang.ThreadLocal$ThreadLocalMap$Entry。选择该传入引用类型的引用对象,并切换到分配视图。您将看到实例被分配的位置。有了这些信息,您可以决定是否可以解决问题。

enter image description here


2
据我所见,JProfiler似乎不是一个免费的开源解决方案。你能推荐一些其他的替代品吗? - Raghav
1
@Raghav Eclipse内存分析工具也非常适用于分析堆转储文件。http://www.eclipse.org/mat/ - Phil

8

Mattias Jiderhamn撰写了一篇非常优秀的6部分文章,清晰地解释了类加载器泄漏的理论和实践。更好的是,他还发布了一个jar文件,我们可以将其包含在war文件中。我已经在我的Web应用上尝试过它,这个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>

更多信息,请参考 托管在GitHub上的项目主页 或者 他的文章系列的第六部分


4
创建线程时如果没有正确清理,最终会耗尽内存 - 我已经遇到过这种情况。那些仍在寻找快速解决方案/解决方法的人可以尝试以下操作:
  • 如果运行独立的Tomcat,请关闭 javaw.exe 或包含它的进程。
  • 如果从Eclipse运行,请关闭 eclipse.exe 和 java.exe 或包含它们的进程。
  • 如果问题仍未解决,请检查任务管理器,可能会显示使用最高内存的进程 - 进行分析并结束该进程。
执行上述操作后,您应该能够重新部署并无内存问题地进行下一步操作。

2

1
我建议在ServletRequestListener中初始化线程局部变量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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接