【问题标题】:Memory leak when redeploying application in Tomcat在 Tomcat 中重新部署应用程序时内存泄漏
【发布时间】:2017-06-02 19:13:54
【问题描述】:

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

  1. 我创建了堆转储。
  2. 然后我发送了 Http 请求,并在服务的方法中打印了当前线程及其类加载器。然后我调用了 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,因为没有任何旧的线程或对象。

堆转储 1

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


发送 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”设置为任何基类加载器)。

堆转储 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”?

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

【问题讨论】:

    标签: java multithreading tomcat tomcat7 jvisualvm


    【解决方案1】:

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

    【讨论】:

    • 我们使用 Java EE 服务器已有 8 年了,我们编写了一个程序,在生产环境中每次部署时都会重新启动应用服务器
    【解决方案2】:

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

    我希望您已经浏览过这些页面:

    Memory leak in a Java web application

    Tomcat Fix Memory Leak?

    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 那样轻量级,但之后的生活会更加轻松。

    【讨论】:

    • 我同意你的看法,这是一个已知的 Tomcat 问题。但是我们仍然在生产中使用 Tomcat,因为它是轻量级和高效的。简单地说,我们从不在生产 Tomcat 上重新部署应用程序,而只是重新启动 Tomcat 容器。这是有道理的,因为它们是高度使用的应用程序,并且容器的开销是可以接受的。
    • @SergeBallesta - 我在论坛上看到很多人谈论在重新部署时发现泄漏,但没有人说 - 只需重新启动 tomcat 并忘记重新部署泄漏。新部署后人们不愿意重启 Tomcat 的原因可能是什么?
    • @SergeBallesta - 好的,我在这个链接上得到了答案 - 问题是当您在同一台服务器上运行多个应用程序时,您需要重新部署而不重新启动 stackoverflow.com/q/2344964/2181576
    • 我不同意生产 Tomcat 问题。 Tomcat 是如此的轻量级,以至于在生产环境中停止和创建一个新的 tomcat 实例通常需要几毫秒。为什么要经常重新部署 Tomcat?为什么你会每周发布不止一次新版本?
    【解决方案3】:

    我们有数百个 Tomcat 实例在多个环境(也包括生产环境)中运行,我们发现解决此问题的唯一合理解决方案是在设定的时间每天停止并重新启动每个 Tomcat(在夜间)。

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        类似于此处解释的内容:ThreadLocal & Memory Leak

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

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

        希望对你有帮助!

        【讨论】:

          【解决方案6】:

          检查是否有 ThreadLocal 使用会阻止您的 ClassLoader 被垃圾收集。要么删除 ThreadLocal 值中对你的类的引用,要么使用 https://github.com/codesinthedark/ImprovedThreadLocal 而不是 ThreadLocal

          【讨论】:

            猜你喜欢
            • 2011-12-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-02
            • 1970-01-01
            相关资源
            最近更新 更多