我有部署在Tomcat 7.0.70中的WebApplication。我模拟了以下情况:
的结果
线程转储
在下面的屏幕上,您可以看到在我点击"重新部署"之后,所有线程(与此Web应用程序相关联)都被杀死,除了线程" http-apr-8081-exec -10&#34 ;.当我设置Tomcat的属性" renewThreadsWhenStoppingContext == true"时,你可以看到一段时间后这个帖子(" http-apr-8081-exec-10")是已经创建了kill和新线程(http-apr-8081-exec-11)而不是它。因此,在创建堆转储3之后,我没有期望拥有旧的WCL,因为没有任何旧的线程或对象。
堆叠转储1
在以下两个屏幕上,您可以看到当应用程序运行时,只有一个WCL(其参数"已启动" = true)。 线程" http-apr-8081-exec-10"有了contextClassLoader = URLClassLoader(因为它在Tomcat的池中)。 我只谈到这个帖子,因为你能够看到这个线程将处理我未来的HTTP请求。
发送HTTP请求
现在我发送HTTP请求,并在我的代码中获取有关当前线程的信息。您可以看到我的请求由线程处理&#34; http-apr-8081-exec-10&#34; < / p>
дек 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
然后我点击&#34;重新部署我的网络应用程序&#34;我在控制台中收到以下消息。
дек 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 dump 2
在以下屏幕上,您可以看到WebAppClassLoader有两个实例。其中一个(编号#1)是旧的(其属性&#34;已启动&#34; = false)。 WCL#2是在重新部署应用程序后创建的(其属性&#34;已启动&#34; = true)。 我们审查的主题有contextClassLoader =&#34; org.apache.catalina.loader.WebappClassLoader&#34;。 为什么?我希望看到contextClassLoader =&#34; java.net.URLClassLoader&#34; (毕竟,当任何线程完成其工作时,它将返回到Tomcat的池中 及其属性&#34; contextClassLoader&#34;设置为任何基类类加载器。
Heapd dump 3
你可以看到没有线程&#34; http-apr-8081-exec-10&#34;,但是有线程&#34; http-apr-8081-exec-11&#34 ;它有contextClassLoader =&#34; WebappClassLoader&#34;
(为什么不URLClassLoader?)。
最后我们有以下内容:有线程&#34; http-apr-8081-exec-11&#34;它具有WebappClassLoader#1的引用。 当我制作&#34;最近的GC Root&#34;在WCL#1上,我将看到线程11的引用。
问题。
如何在线程完成其工作后强行告诉Tomcat返回旧值contextClassLoader(URLClassLoader)?
如何确保Tomcat不会复制旧值&#34; contextClassLoader&#34;在线程更新期间?
也许,你知道解决我问题的其他方法吗?
答案 0 :(得分:5)
Tomcat通常不是生产环境的好选择。我在一些生产应用程序上使用Tomcat,我发现即使堆大小和其他配置都已正确设置 - 每次重新加载应用程序时,内存消耗也会上升。在您不重新启动tomcat服务之前,内存未完全回收。我们测试了所有这些实验,例如清除日志,重新部署所有应用程序,每月定期重新启动tomcat,或者在最繁忙时段定期重启一周。但最后我不得不说我们已将生产环境转移到Glassfish和WebSphere。
我希望你已经浏览过这些页面:
Memory leak in a Java web application
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那样轻巧但是生活后更容易。
答案 1 :(得分:1)
根据我对这个问题的经验,阻止tomcat正确使用GC旧类加载器的原因是我正在使用的一些ThreadLocal
框架正在创建(并且没有正确处理)。
类似于此处解释的内容:ThreadLocal & Memory Leak
我试图正确地完成这个ThreadLocal
并且我的泄漏减少了很多。它仍在泄漏,但我可以处理比以前多10倍的重新安装。
我肯定会检查你的内存转储到可能以某种方式连接到ThreadLocal
的对象(它们非常常见,特别是如果你使用某些东西来控制事务或任何线程隔离的东西)。
我希望它有所帮助!
答案 2 :(得分:0)
tomcat的重新发布中的内存泄漏是一个非常古老的问题。 解决它的唯一真正方法是重启tomcat而不是重新部署应用程序。如果您有多个应用程序,则需要在不同的端口上运行多个tomcat服务并将其与nginx连接。
答案 3 :(得分:0)
我们在多个环境(也是生产环境)中运行了数百个Tomcat实例,我们在此问题上找到的唯一合理解决方案是在设定的时间每天停止并重启每个Tomcat (在夜晚)。
我们尝试了许多技巧,但这是我们正常运行时间要求的持久解决方案。
答案 4 :(得分:0)
Tomcat通常不是生产环境的好选择。我在一些生产应用程序上使用Tomcat,我发现即使堆大小和其他配置都已正确设置 - 每次重新加载应用程序时,内存消耗也会上升。在您不重新启动tomcat服务之前,内存未完全回收。我们测试了所有这些实验,例如清除日志,重新部署所有应用程序,每月定期重新启动tomcat,或者在最繁忙时段定期重启一周。但最后我不得不说我们已将生产环境转移到Glassfish和WebSphere。
答案 5 :(得分:0)
检查ThreadLocal使用,以防止您的ClassLoader被垃圾回收。在ThreadLocal值中删除对类的引用,或使用https://github.com/codesinthedark/ImprovedThreadLocal而不是ThreadLocal