有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

未收集java Groovy类,但没有内存泄漏迹象

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

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

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

然后通过清除classloader缓存、metaregistry等动态地重新加载groovy类:

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

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

如果我运行它,将maxpermsize设置为128m,那么我会得到泄漏行为,并显示permgen错误:

memory profile for 128m permgen

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

enter image description here

有没有人遇到过类似的行为?或者你有什么想法?在第一个例子中,内存使用量是逐步增加而不是稳步增加,这似乎也很奇怪


共 (1) 个答案

  1. # 1 楼答案

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

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

    除了loadClass调用可能导致创建新类和以某种方式引用该类的meta类注册表之外,还有更多的类具有可能的引用。例如,Groovy中的callsite缓存也涉及对类的软引用。而且,如果反射经常调用某些东西(Groovy可能必须这样做),那么可能会生成帮助器类来加速反射。后面这一步是由JDK完成的,而不是Groovy

    我必须纠正一个错误:元类不是真正的类,不能使用permgen。但它们引用了采用permgen的类。因此,如果元类是硬引用的,则该类将保持不变。在IBM JDK中有一个有趣的“特性”,它认为如果一个类是硬引用的,它是可卸载的,即使做这个引用的对象本身也是软引用的一部分。p>

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