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

Java 在Tomcat中重新部署应用程序时内存泄漏,java,multithreading,tomcat,tomcat7,jvisualvm,Java,Multithreading,Tomcat,Tomcat7,Jvisualvm,我有一个部署在Tomcat7.0.70中的WebApplication。我模拟了以下情况: 我创建了堆转储 然后我发送了Http请求,并在服务的方法中打印了当前线程及其类加载器。然后我调用了Thread.currentThread.sleep(10000) 同时,我在Tomcat的管理页面中单击了“取消部署此应用程序” 我创建了新的堆转储 几分钟后,我创建了新的hep转储 结果 线程转储 在下面的屏幕上,您可以看到,在我单击“重新部署”之后,除了线程“http-apr-8081-exec-1

我有一个部署在Tomcat7.0.70中的WebApplication。我模拟了以下情况:

  • 我创建了堆转储
  • 然后我发送了Http请求,并在服务的方法中打印了当前线程及其类加载器。然后我调用了Thread.currentThread.sleep(10000)
  • 同时,我在Tomcat的管理页面中单击了“取消部署此应用程序”
  • 我创建了新的堆转储
  • 几分钟后,我创建了新的hep转储

  • 结果


    线程转储

    在下面的屏幕上,您可以看到,在我单击“重新部署”之后,除了线程“http-apr-8081-exec-10”之外,所有线程(与此web应用程序关联)都被终止。正如我设置Tomcat的属性“renewThreadsWhenStoppingContext==true”一样,您可以看到,经过一段时间后,这个线程(“http-apr-8081-exec-10”)被终止,并创建了新线程(http-apr-8081-exec-11)。所以我没想到在创建heap dump 3之后会有旧的WCL,因为没有任何旧的线程或对象

    Heapd转储1

    在以下两个屏幕上,您可以看到应用程序运行时只有一个WCL(其参数“started”=true)。 线程“http-apr-8081-exec-10”的contextClassLoader=URLClassLoader(因为它在Tomcat的池中)。 我只谈论这个线程,因为您将能够看到这个线程将处理我未来的HTTP请求


    发送HTTP请求

    现在,我发送HTTP请求,并在代码中获得有关当前线程的信息。您可以看到,我的请求正在由线程“HTTP-apr-8081-exec-10”处理

    然后单击“重新部署我的web应用程序”,我在控制台中得到以下消息

     дек 23, 2016 9:28:27 AM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
     SEVERE: The web application [/hdi] appears to have started a thread named [http-apr-8081-exec-10] but has failed to stop it. This is very likely to create a memory leak.
    
    Heapd转储2

    在以下屏幕上,您可以看到WebAppClassLoader有两个实例。其中一个(数字#1)是旧的(其属性“started”=false)。 WCL#2是在重新部署应用程序后创建的(其属性“started”=true)。 我们回顾的线程有contextClassLoader=“org.apache.catalina.loader.WebappClassLoader”。 为什么?我希望看到contextClassLoader=“java.net.URLClassLoader”(毕竟,当任何线程完成其工作时,它都会返回到Tomcat的池中 其属性“contextClassLoader”设置为任何基本类加载器)

    Heapd转储3

    您可以看到,没有线程“http-apr-8081-exec-10”,但有线程“http-apr-8081-exec-11”,它有contextClassLoader=“WebappClassLoader” (为什么不使用URLClassLoader?)。

    最后我们有以下内容:有一个线程“http-apr-8081-exec-11”,它引用了WebappClassLoader#1。 显然,当我在WCL#1上创建“最近的GC根”时,我将看到线程11的引用

    问题。

    在线程完成其工作后,我如何强制Tomcat返回旧值contextClassLoader(URLClassLoader)

    如何确保Tomcat在线程更新期间不复制旧值“contextClassLoader”


    也许,你知道解决我问题的其他方法吗?

    tomcat重新开发中的内存泄漏是一个非常老的问题。
    解决这个问题的唯一真正方法是重新启动tomcat,而不是重新部署应用程序。如果您有多个应用程序,则需要在不同端口上运行多个tomcat服务,并将其与nginx连接。

    在生产环境中,tomcat通常不是一个好的选择。我在一些生产应用程序上使用Tomcat,发现即使堆大小和其他配置设置正确,每次重新加载应用程序时,内存消耗也会不断增加。除非重新启动tomcat服务,否则内存不会完全回收。我们测试了所有这些实验,比如,清除日志,重新部署所有应用程序,定期在最不繁忙的时间每月或每周重启一次tomcat。但最后我不得不说,我们已经将生产环境转移到Glassfish和WebSphere

    我希望您已经阅读了以下几页:


    如果您的web应用程序没有与Tomcat紧密耦合,那么您可以考虑使用另一个web容器。现在我们甚至在开发机器和生产中使用Glassfish,在我们做出这个决定的那天,我们节省了很多时间。尽管Glassfish和其他类似的服务器在启动时需要更多的时间,因为它们不像Tomcat那样轻量级,但在以后的工作中会更容易一些。

    我们有数百个Tomcat实例在多个环境中运行(还有生产环境)对于这个问题,我们找到的唯一合理的解决方案是每天(在夜间)在设定的时间停止并重新启动每个Tomcat


    我们已经尝试了很多技巧,但这是我们正常运行时间要求的持久解决方案。

    在生产环境中,Tomcat通常不是一个好的选择。我在一些生产应用程序上使用Tomcat,发现即使堆大小和其他配置设置正确,每次重新加载应用程序时,内存消耗也会不断增加。除非重新启动tomcat服务,否则内存不会完全回收。我们测试了所有这些实验,比如,清除日志,重新部署所有应用程序,定期在最不繁忙的时间每月或每周重启一次tomcat。但最后我不得不说,我们已经将生产环境转移到Glassfish和WebSphere上。

    根据我在这个问题上的经验,阻止tomcat正确使用GC旧类加载器的是一些
    ThreadLocal
    s一些我正在使用的框架正在创建(并且没有正确处理)

    类似于此处所述的内容:

    我试图正确地完成这个
    线程
    
     дек 23, 2016 9:28:27 AM org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
     SEVERE: The web application [/hdi] appears to have started a thread named [http-apr-8081-exec-10] but has failed to stop it. This is very likely to create a memory leak.