当类本身被垃圾收集时,静态变量应该被垃圾收集,而当它的类加载器被垃圾收集时也是如此。
您可以通过让应用程序类加载器未加载的任何内容引用您的任何类(或类的实例)来轻松创建内存泄漏。寻找你没有正确删除的回调监听器等(内部/匿名类很容易被忽略)。
对其中一个类的单个引用会阻止其类加载器,进而阻止该类加载器加载的任何类被垃圾收集。
编辑,泄漏阻止所有类 GC 的对象的示例:
MemoryMXBean mx = ManagementFactory.getMemoryMXBean();
NotificationListener nl = new NotificationListener() { ... };
((NotificationEmitter) mx).addNotificationListener(nl, ..., ...);
如果您使用存在于应用程序范围之外的对象(此处为 MemoryMXBean)注册侦听器(此处为 NotificationListener),则您的侦听器将保持“活动”状态,直到它被显式删除。由于您的侦听器实例持有对其 ClassLoader(您的应用程序类加载器)的引用,您现在已经创建了一个强大的引用链,以防止类加载器的 GC,进而阻止它加载的所有类,以及这些类持有的任何静态变量。
Edit2:基本上你需要避免这种情况:
[Root ClassLoader]
|
v
[Application ClassLoader]
|
v
(Type loaded by Root).addSomething()
运行应用程序服务器的 JVM 已经通过根类加载器(可能还有应用程序服务器)加载了 JRE。这意味着这些类永远不会符合 GC 的条件,因为其中一些类总是存在实时引用。应用程序服务器将在一个单独的类加载器中加载您的应用程序,当您的应用程序重新部署(或至少应该)时,它将不再持有对它的引用。但是您的应用程序将与应用程序服务器(至少是 JRE,但通常还有应用程序服务器)共享来自至少 JRE 的所有类。
在假设的情况下,当应用程序服务器要创建一个单独的类加载器(没有父类,实际上是第二个根类加载器)并尝试第二次加载 JRE(作为您的应用程序的私有),这会导致很多问题. 打算成为单例的类将存在两次,并且两个类层次结构将无法保存另一个的任何引用(由不同的类加载器加载的同一类对 JVM 的不同类型造成)。他们甚至不能使用 java.lang.Object 作为相应“其他”类加载器对象的引用类型。