rhi*_*nds 5 java groovy memory-leaks
我有一个Java类,它使用自定义类加载器动态重新加载groovy类,我看到一些奇怪的行为,某些类没有被收集,但随着时间的推移,它不会泄漏内存(例如,perm gen不会无限期地继续增长).
在我的java代码中,我正在加载这样的类(为简单起见,删除了样板文件):
Class clazz = groovyClassLoader.loadClass(className, true, false, true);
instance = clazz.newInstance();
然后我通过清除类加载器缓存,metaregistry等动态重新加载groovy类:
for (Class c : groovyClassLoader.getLoadedClasses()){
     GroovySystem.getMetaClassRegistry().removeMetaClass(c);
}
groovyClassLoader.clearCache();
现在,如果我只是循环遍历这些代码,不断加载然后重新加载我的groovy类,我会看到奇怪的行为(我的测试代码实际上只是循环重载过程 - 它没有对任何创建的对象做任何事情,所以上面的代码中的实例只是本地的,所以应该对GC有好处).
如果我运行它,将maxpermsize设置为128m然后我得到泄漏行为和OOM permgen错误:

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

有没有人遇到任何类似的行为?还是有什么想法?在第一个例子中,内存使用率逐步上升而不是稳定增长,这似乎也很奇怪.
您看到的锯齿图案是一直分配和释放内存的典型情况。您添加了代码来清除缓存,但这并不自动意味着该类将被收集。在尝试对寿命较长的对象进行正常垃圾回收之前,JVM 往往会大量增加内存。删除类的次数更少,通常仅在完整的 gc 运行期间进行。这可能会导致恼人的情况,即 permgen 已满,并且抛出 OOME,即使可以收集类。确切的行为似乎因版本而异。
反正。仅仅因为该类不再被引用,并不意味着它会立即被收集。相反,permgen 可能会增长到最大值,然后卸载类。
除了您的 loadClass 调用可能导致创建新类和元类注册表以某种方式引用该类之外,还有更多可能引用的类。例如,Groovy 中的调用点缓存也涉及到类的软引用。如果某些东西被反射调用得足够频繁(Groovy 可能必须这样做),则可能会生成帮助程序类来加速反射。后一个是由 JDK 完成的,而不是 Groovy。
我必须做一个更正:元类不是真正的类,并且不能采用永久生成。但他们引用了采用 permgen 的类。因此,如果元类被硬引用,则该类将保留。IBM JDK 中有一个有趣的“功能”,如果一个类被硬引用,它就会认为该类是不可加载的,即使执行该引用的对象本身是软引用的一部分。
为了更完整地解释上述行为,我需要 JVM 的输出来进行类加载和卸载。我假设该应用程序理论上可以在 128MB 中运行。如果您查看 16:17:30 之前的低点和之前的 128MB 图表,您可能会注意到之前的一个并不像另一个那么低。这意味着在该时间点之前的代码确实比之前的时间点卸载了更多的类。JVM 可以自由决定何时删除某个类,并且并不总是删除理论上可以卸载的所有类。您必须在可以卸载的类和性能之间进行权衡。