静态变量,Tomcat和内存泄漏

Luk*_*lin 6 java tomcat memory-leaks jprofiler

我正在调试我在Tomcat应用程序中使用多年的问题 - 重新启动应用程序时导致的内存泄漏,因为Webapp类加载器无法进行GC.我用JProfiler拍摄堆的快照,似乎至少有一些我的静态变量没有被释放.

某些类具有静态最终成员,该成员在首次加载类时初始化,并且因为它是最终的,所以我无法在应用程序关闭时将其设置为null.

静态最终变量是Tomcat中的反模式,还是我错过了什么?我刚刚开始使用JProfiler 8,所以我可能误解了传入的引用告诉我的内容.

干杯!

卢克

Dur*_*dal 5

当类本身被垃圾回收时,静态变量应该被垃圾回收,反过来,当它的类加载器被垃圾回收时也是如此。

您可以通过让应用程序类加载器未加载的任何内容引用您的任何类(或类的实例)来轻松创建内存泄漏。寻找您没有正确删除的回调侦听器等(内部/匿名类很容易被忽略)。

对您的一个类的单一引用会阻止其类加载器,进而阻止该类加载器加载的任何类被垃圾收集。

编辑,泄漏防止所有类的 GC 的对象的示例:

MemoryMXBean mx = ManagementFactory.getMemoryMXBean();
NotificationListener nl = new NotificationListener() { ... };
((NotificationEmitter) mx).addNotificationListener(nl, ..., ...);
Run Code Online (Sandbox Code Playgroud)

如果您使用存在于应用程序范围之外的对象(此处为 MemoryMXBean)注册一个侦听器(此处为 NotificationListener),则您的侦听器将保持“活动”状态,直到它被明确删除。由于您的侦听器实例持有对其 ClassLoader(您的应用程序类加载器)的引用,您现在已经创建了一个强大的引用链,以防止类加载器的 GC,进而阻止它加载的所有类,以及这些类持有的任何静态变量。

Edit2:基本上你需要避免这种情况:

[Root ClassLoader]
       |
       v
      [Application ClassLoader]
               |
               v
               (Type loaded by Root).addSomething()
Run Code Online (Sandbox Code Playgroud)

运行应用服务器的 JVM 已经通过根类加载器(也可能是应用服务器)加载了 JRE。这意味着这些类永远不会有资格进行 GC,因为总会有对其中一些类的实时引用。应用程序服务器将在一个单独的类加载器中加载您的应用程序,当您的应用程序被重新部署(或至少应该)时,它将不再持有引用。但是您的应用程序将与应用程序服务器(至少是 JRE,但通常还有应用程序服务器)共享至少来自 JRE 的所有类。

在假设的情况下,当应用程序服务器要创建一个单独的类加载器(没有父类,实际上是第二个根类加载器)并尝试第二次加载 JRE(对于您的应用程序来说是私有的)时,它会导致很多问题. 打算成为单例的类将存在两次,并且两个类层次结构将无法保存另一个类的任何引用(由不同的类加载器加载的同一个类为 JVM 提供不同的类型)。他们甚至不能使用 java.lang.Object 作为各自“其他”类加载器对象的引用类型。


Mar*_*mas 5

它是几年前的,但我在JavaOne 上的演示完全涵盖了这个主题.找到泄漏的关键步骤在幻灯片11中,但是有很多背景信息也可能有用.

简短版本是:

  • 触发泄漏
  • 强制GC
  • 使用分析器查找具有属性started = false的org.apache.catalina.loader.WebappClassLoader的实例
  • 跟踪该对象的GC根源 - 这些是您的泄漏

正如我在演示文稿中指出的那样,找到漏洞是一回事,找到触发它们的东西可能会困难得多.

我建议运行最新的稳定Tomcat版本,因为我们总是在改进内存泄漏检测和防范代码,并且生成的警告和错误也可能提供一些指示.

  • 找到了.sun.awt.AWTAppContext - 需要确保Webapp类加载器上方的内容正在抓取上下文.http://cdivilly.wordpress.com/2012/04/23/permgen-memory-leak/ (2认同)