2013-10-27 104 views
5

我正在调试多年来在Tomcat应用程序中出现的问题 - 由于Web应用程序类加载程序无法进行GC化,因此在重新启动应用程序时导致内存泄漏。我已经用JProfiler拍了堆快照,似乎至少有一些我的静态变量没有被释放。静态变量,Tomcat和内存泄漏

某些类有一个静态最终成员,它在第一次加载类时被初始化,并且因为它是最终的,所以在应用程序关闭时我不能将它设置为null。

静态最终变量是Tomcat中的反模式,还是我错过了某些东西?我刚开始讨论JProfiler 8,所以我可能会误解传入的参考资料告诉我什么。

干杯!

卢克

回答

5

它是从几年前,但我在JavaOne给出的presentation恰恰涵盖了这个话题。查找泄漏的关键步骤在幻灯片11中,但也有许多背景信息可能也有用。

简短的版本是:

  • 触发泄漏
  • 力GC
  • 使用分析器找到了物业开始org.apache.catalina.loader.WebappClassLoader的实例= FALSE
  • 跟踪该对象的GC根 - 这些都是你的漏洞

正如我注意到在演示文稿中,找到个泄漏是一回事,找到触发它们会更困难的原因。

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

+0

我在7.0.47,不幸的是,类加载器的GC根是几百。只有大约12个实例存在,它们都是类的静态变量 - 除Log4j 1.x类外。也许这就是让类加载器保持活力的原因,因此保持静态? –

+0

听起来很合理。你是否从ServletContextLister.destroy()方法调用LogManager.shutdown()来清理log4j? –

+0

我是,但仍然有几个log4j类左边躺着。我重构我的应用程序以使用tomcat生命周期侦听器加载log4j并将log4j jar移动到$ TOMCAT/lib中,以便webapp类加载器不会加载它。应用程序关闭后留下的所有内容都是我的应用程序,并且跟踪GC根目录仅指向静态和类文件的链接。 :( –

1

Blog可以给你在你的应用程序的内存泄漏的想法。

+0

虽然这很有帮助,但我并没有引用外部代码。这只是我的webapp中的代码,全部由相同的类加载器加载。 –

4

当类本身被垃圾收集时,静态变量应该被垃圾收集,而类垃圾收集器则是垃圾收集。

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

对其中一个类的单引用会阻止其类加载器,并且该类加载器加载的任何类都将被垃圾回收。

编辑,例如漏水,防止所有类GC对象:

MemoryMXBean mx = ManagementFactory.getMemoryMXBean(); 
NotificationListener nl = new NotificationListener() { ... }; 
((NotificationEmitter) mx).addNotificationListener(nl, ..., ...); 

如果你(这里MemoryMXBean)注册一个监听器(的NotificationListener这里)与存在ouside您的应用程序范围的对象,你的听众将保持'生活'直到其明确删除。由于您的侦听器实例持有对其ClassLoader(您的应用程序类加载器)的引用,因此您现在已创建了一个强大的引用链,用于防止类加载器的GC,以及它加载的所有类以及这些类所持有的任何静态变量。

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

[Root ClassLoader] 
     | 
     v 
     [Application ClassLoader] 
       | 
       v 
       (Type loaded by Root).addSomething() 

的JVM上运行的应用程序服务器加载了JRE槽根类装载器(也可能是应用程序服务器,太)。这意味着这些课程永远不会成为GC的资格,因为它们中的一些将始终有实时参考。应用程序服务器会将您的应用程序加载到一个单独的类加载器中,以至于当您的应用程序重新部署(或至少应该)时,它将不再持有引用。但是,您的应用程序将至少将JRE中的所有类与应用程序服务器(至少是JRE,但通常也是应用程序服务器)共享。

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

+0

有没有什么方法可以使用JProfiler轻松找到它?我查看了保留的引用,并且尽可能地告诉他们它们只是类中的静态变量。可能的一个线索是它们是由ServletContextListener加载的类,但它本身似乎已被收集。 –

+0

@LukeKolin我不确定JProfiler究竟显示什么,但原则上你只需从应用程序外部寻找类,并从应用程序中引用类。如果你拥有的所有东西都在com.mydomain包的下面,那么任何持有com.mydomain引用的引用,都不是来自com.mydomain,而是被调查的候选者。如果幸运的话,保留对象类的名称已经告诉你它已经被泄露的地方。 – Durandal

+0

@Durandal您可以举一个例子:“您可以通过让应用程序类加载器没有加载任何类(或类的实例)的引用来轻松地创建内存泄漏。”我只是想知道什么时候这是可能的,哪些类加载器加载类如果不是应用程序类加载器? –