当类本身被垃圾收集时,静态变量应该被垃圾收集,而类垃圾收集器则是垃圾收集。
您可以通过应用程序类加载器引用任何类(或类的实例)的任何未加载来轻松地创建内存泄漏。寻找像回调监听器等东西,你没有正确删除(内部/匿名类很容易被忽略)。
对其中一个类的单引用会阻止其类加载器,并且该类加载器加载的任何类都将被垃圾回收。
编辑,例如漏水,防止所有类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作为相应的“其他”类加载器对象的引用类型。
我在7.0.47,不幸的是,类加载器的GC根是几百。只有大约12个实例存在,它们都是类的静态变量 - 除Log4j 1.x类外。也许这就是让类加载器保持活力的原因,因此保持静态? –
听起来很合理。你是否从ServletContextLister.destroy()方法调用LogManager.shutdown()来清理log4j? –
我是,但仍然有几个log4j类左边躺着。我重构我的应用程序以使用tomcat生命周期侦听器加载log4j并将log4j jar移动到$ TOMCAT/lib中,以便webapp类加载器不会加载它。应用程序关闭后留下的所有内容都是我的应用程序,并且跟踪GC根目录仅指向静态和类文件的链接。 :( –