避免PermGen内存溢出和GC超时限制

5
我正在尝试在运行时生成类并加载它们。
我使用一个ClassLoader对象来加载这些类。由于我不想用完PermGen内存,我不时地取消对类加载器的引用,并创建一个新的类加载器来加载要使用的新类。这似乎很好用,我也没有遇到PermGen内存不足的情况。
但是问题是,当我这样做一段时间后,会出现以下错误:
java.lang.OutOfMemoryError: GC overhead limit exceeded 

我的问题是,为了避免错误,我应该在什么时候取消对类加载器的引用呢?
我应该在代码中监控PermGen的使用情况,当PermGen的使用接近极限时,取消对类加载器的引用并调用System.gc()吗?
还是应该采取不同的方法?
谢谢。

3个回答

6

这个问题没有唯一正确的答案。

一方面,如果解除类加载器可以解决您的permgen泄漏问题,那么您应该继续这样做。

另一方面,“GC超时限制已超过”错误意味着您的应用程序在垃圾收集上花费了太多时间。在大多数情况下,这意味着您的堆栈太满了。但这可能意味着以下两种情况之一:

  • 堆栈对于您的应用程序要求来说太小了。

  • 您的应用程序存在内存泄漏。

您可以假设问题是前者,只需增加堆栈大小即可。但是,如果真正的问题是后者,那么增加堆栈大小只是在拖延不可避免的问题...而正确的做法是查找修复内存泄漏。


不要调用System.gc()。它没有帮助。


我正在使用jvisualvm监控堆使用情况,最大大小为1 Gb,使用量在200 mb到600 mb之间变动,因此远未达到极限。 - pablo
@otonakav - 或许你的GC调优参数存在问题。 - Stephen C

1
我曾经遇到过与类卸载相关的类似情况。
我使用多个类加载器来模拟JUnit测试中的多个JVM(这通常用于与Oracle Coherence集群一起工作,但我也成功地将此技术用于在JVM内启动多节点HBase / Hadoop集群)。
由于各种原因,测试可能需要重新启动这样的“虚拟”JVM,这意味着放弃旧的ClassLoader并创建新的ClassLoader。
有时,如果您进行Full GC,则JVM会延迟类卸载事件,这会导致以后出现各种问题。
我发现一个有用的强制JVM收集PermSpace的技巧是以下内容。
public static void forcePermSpaceGC(double factor) {
    if (PERM_SPACE_MBEAN == null) {
        // probably not a HotSpot JVM
        return;
    }
    else {
        double f = ((double)getPermSpaceUsage()) / getPermSpaceLimit();
        if (f > factor) {

            List<String> bloat = new ArrayList<String>();
            int spree = 0;
            int n = 0;
            while(spree < 5) {
                try {
                    byte[] b = new byte[1 << 20];
                    Arrays.fill(b, (byte)('A' + ++n));
                    bloat.add(new String(b).intern());
                    spree = 0;
                }
                catch(OutOfMemoryError e) {
                    ++spree;
                    System.gc();
                }
            }
            return;
        }
    }
}   

完整源代码

我正在使用intern()将字符串填充到PermSpace中,直到JVM收集它们。

但是

  • 我正在使用该技术进行测试
  • 不同的硬件/ JVM版本组合可能需要不同的阈值,因此通常更快地重新启动整个JVM而不是强制其正确收集所有垃圾

1
你是否多次加载同一个类?因为你应该缓存已加载的类。
如果没有,你加载了多少个类?如果它们很多,你可能需要限制加载类的数量(这个数字可以基于堆大小或基于加载类所需的内存量),并在加载下一个类时丢弃最不常用的类。

可能设置一个限制可以减少我取消引用类加载器的次数,以便垃圾回收不会运行太多次,但我假设如果有足够的堆和PermGen,GC就不会运行,只因为我取消引用了一个类加载器,特别是如果它将超过开销限制。 - pablo
我想指出的是问题可能在于类加载器的使用,而不是类加载器本身。 - Bruno Costa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接