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

我有WebApplication部署在Tomcat 7.0.70中。我模拟了以下情况:

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


结果


线程转储

在下面的屏幕上,您可以看到,在我单击“重新部署”后,除了线程“http-apr-8081-exec-10”之外,所有线程(与此Web应用程序关联)都被杀死了。当我设置Tomcat的属性“renewThreadsWhenStoppingContext == true”时,你可以看到一段时间后这个线程(“http-apr-8081-exec-10”)被杀死,并创建了新线程(http-apr-8081-exec-11)而不是它。所以我没想到在创建堆转储3之后会有旧的WCL,因为没有任何旧的线程或对象。

enter image description here

堆转储 1

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

enter image description here

enter image description here

发送 HTTP 请求

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

дек 23, 2016 9:28:16 AM c.c.c.f.s.r.ReportGenerationServiceImpl INFO:  request has been handled in 
   thread = http-apr-8081-exec-10,  its contextClassLoader = WebappClassLoader
   context: /hdi
   delegate: false
   repositories:
   /WEB-INF/classes/
   ----------> Parent Classloader: java.net.URLClassLoader@4162ca06

然后,我单击“重新部署我的 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.

堆转储 2

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

enter image description here

enter image description here

enter image description here

堆转储 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的引用。

enter image description here

enter image description here

问题。

我怎么能强行说 Tomcat 在线程完成其工作后返回旧值 contextClassLoader (URLClassLoader)?

如何确保Tomcat在线程续订期间不会复制旧值“contextClassLoader”?

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


答案 1

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

我希望你已经浏览了这些页面:

Java Web 应用程序中的内存泄漏

雄猫修复内存泄漏?

https://developers.redhat.com/blog/2014/08/14/find-fix-memory-leaks-java-application/

http://www.tomcatexpert.com/blog/2010/04/06/tomcats-new-memory-leak-prevention-and-detection

如果您的Web应用程序没有与Tomcat紧密耦合,那么您可以考虑使用另一个Web容器。现在,我们甚至在开发机器和生产中使用Glassfish,在我们做出这个决定的那一天,我们节省了大量的时间。虽然Glassfish和其他这样的服务器在开始时需要更多的时间,因为它们不像Tomcat那样轻巧,但生活更容易。


答案 2

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

类似于这里解释的内容:ThreadLocal和Memory Leak。

我试图正确地完成这个s,我的泄漏减少了很多。它仍然在泄漏,但我可以处理比以前多10倍的重新部署。ThreadLocal

我肯定会检查你的内存转储到可以以某种方式连接到s的对象(它们非常常见,特别是如果你使用某些东西来控制事务或任何线程隔离的东西)。ThreadLocal

我希望它有帮助!