6

我有一个 Java 类,它使用自定义类加载器动态重新加载 groovy 类,我看到一些奇怪的行为,一些类没有被收集,但随着时间的推移它不会泄漏内存(例如 perm gen 不会继续无限增长)。

在我的 java 代码中,我正在加载这样的类(为简单起见,删除了样板文件):

Class clazz = groovyClassLoader.loadClass(className, true, false, true);
instance = clazz.newInstance();

然后我通过清除类加载器缓存、元注册表等动态地重新加载 groovy 类:

for (Class c : groovyClassLoader.getLoadedClasses()){
     GroovySystem.getMetaClassRegistry().removeMetaClass(c);
}
groovyClassLoader.clearCache();

现在,如果我只是循环这段代码,不断加载然后重新加载我的 groovy 类,我会看到奇怪的行为(我的测试代码实际上只是在循环重新加载过程 - 它对创建的任何对象等都没有做任何事情,所以上面代码中的实例只是本地的,所以应该对 GC 有好处)。

如果我运行它,将 maxpermsize 设置为 128m 然后我得到泄漏行为并且它 OOM permgen 错误:

128m permgen 的内存配置文件

但是,如果我再次运行它并将 maxpermsize 增加到 256m,那么一切都很好,它可以永远运行(这个图像是 1 小时,但我已经运行了整晚,做了数千次重新加载):

在此处输入图像描述

有没有人遇到过类似的行为?或有什么想法?同样奇怪的是,在第一个示例中,内存使用量是逐步增加而不是稳定增加。

4

1 回答 1

4

您看到的锯齿模式对于一直分配和释放的内存是典型的。您添加了清除缓存的代码,但这并不意味着将自动收集该类。JVM 甚至在尝试对更长生命周期的对象进行正常的垃圾收集之前就倾向于增加内存。删除类的操作更少,通常仅在完整的 gc 运行期间完成。这可能会导致令人讨厌的情况,即 permgen 已满并抛出 OOME,即使已经收集了类。确切的行为似乎因版本而异。

反正。仅仅因为该类不再被引用,并不意味着它被立即收集。相反,permgen 可能会增长到最大值,然后卸载类。

除了您的 loadClass 调用可能导致创建新类和元类注册表以某种方式引用该类之外,还有更多具有可能引用的类。例如,Groovy 中的调用站点缓存也涉及到类的软引用。如果反射调用的次数足够多(Groovy 可能必须这样做),可能会生成帮助类来加速反射。后一个是由 JDK 完成的,而不是 Groovy。

我必须做的一个更正:元类不是真正的类,不能使用 permgen。但他们引用了采用 permgen 的类。因此,如果元类被硬引用,则该类将保留。IBM JDK 中有一个有趣的“特性”,它确实认为如果一个类被硬引用,则它是不可加载的,即使执行该引用的对象本身是 SoftReference 的一部分。

为了更完整地解释上述行为,我需要 JVM 的输出来进行类加载和卸载。我假设应用程序理论上可以在 128MB 中运行。如果您查看 16:17:30 之前和之前的低点的 128MB 图表,您可能会注意到之前的一个没有另一个低。这意味着直接在该时间代码之前的点确实卸载了比之前更多的类。JVM 可以自由决定何时删除一个类,并且并不总是删除理论上可以卸载的所有类。您必须在尽可能卸载的类和性能之间进行权衡。

于 2014-01-28T15:39:31.210 回答