【发布时间】:2023-03-19 19:38:01
【问题描述】:
我们正在创建多个子类加载器,以将多个子应用程序加载到 Java 应用程序“容器”中,对热部署进行原型设计。当特定类加载器的类路径发生更改(即添加、删除、更新 jar)时,旧的类加载器将被丢弃(未引用),并为新的 jar 类路径创建一个新的类加载器。
更新类路径后,触发热部署,我们进行了堆转储。堆转储(使用内存分析器)表明旧的类加载器没有被垃圾收集。父类加载器中的某些类正在缓存旧的类加载器。调用了以下内容来清除这些缓存:
java.lang.ResourceBundle.clearCache(classLoader);
org.apache.commons.logging.LogFactory.release(classLoader);
java.beans.Introspector.flushCaches();
即使在清除了上述缓存之后,旧的类加载器仍然没有被垃圾回收。对类加载器的其余引用包括以下内容:
- 类加载器加载的类
- java.lang.Package 由类加载器自己创建
- 类加载器自己创建的java.lang.ProtectionDomain
以上都是类加载器中的循环引用,应该会触发垃圾回收。我不确定为什么不是。有谁知道为什么即使使用循环引用,旧的类加载器仍然没有被垃圾收集?
【问题讨论】:
-
您使用哪个 JVM(确切版本)?您是否使用任何可能影响类加载的 JVM 选项?您是否使用 Sun 自己的实现中的任何东西?应用程序是否操作字节码? ... 什么环境,可能会影响类加载?
-
与您的主要问题无关,但您是否考虑过类似 OSGi 之类的东西,而不是做自己的支持热部署的框架?
-
@bfoo 在我们的测试中,我们使用的是 Java 6。没有 JVM 选项。在最简单的情况下,我们没有使用 Sun 的 impl 。没有字节码操作。
-
@stevendick 是的,我们考虑过 OSGi,但对于我们的用例来说,OSGI 似乎有点矫枉过正。要克服的主要障碍是避免不同子应用程序使用的第三方库发生冲突。子应用程序将使用完全等效的库,但在不同的类加载器中,以避免任何潜在的冲突和复杂性。听起来确实会占用太多内存……您使用过 OSGi 吗?如果是这样,您面临的一些障碍是什么?
-
要清楚,使用 Sun 实现我的意思是例如使用带有序列化的套接字通信,例如 RMI/HTTP/SOAP 调用等。这些实现可能使用缓存方法,例如用于序列化问题。这样,您的类(将被卸载)由上面的 ClassLoader 和/或您希望清除的 ClassLoader 引用。这些实现可能会使用对类的强引用,以确保序列化不会引发系统/VM IO 问题。
标签: java garbage-collection classloader memory-leaks