Out of memory error : GC overhead limit exceeded

GC overhead limit exceeded 是指垃圾回收器通过分析当前内存使用趋势,提前抛出异常而不是真正等到内存耗尽再抛出异常。如果真正等到内存耗尽再抛出异常,可能的后果是:我们连保存重要数据的空间都没了。

那么GC是如何分析内存使用趋势来决定是否跑出此异常呢? 默认情况下, 如果内存使用量达到一个阈值之后,GC花费的时间超过 98%, 并且GC回收的内存少于 2%,JVM就会抛出异常。也就是说,GC发现“内存将尽,大厦将倾”,而自己“鞠躬尽瘁”,“费力不讨好”,总做“无用功”的时候,GC就会抛出异常。

明白了异常发生的原理,我们很容易“制造”异常来模拟“车祸”现场。下面的代码随机生成字符串,并把字符串添加到列表。因为这些新创建的字符串无法被GC回收,在内存溢出之前,GC就率先抛出异常了。

运行这段程序使用-Xmx12m参数,把堆设的小一些效果更明显。

package wyf.clonesite;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class BugShooter {
String randomString() {
    StringBuilder builder = new StringBuilder();
    Random r = new Random();
    for (int i = 0; i < 30; i++) {
        builder.append(r.nextInt(100));
    }
    return builder.toString();
}

BugShooter() {
    int cnt = 0;
    try {
        List<String> a = new ArrayList<>();
        while (true) {
            cnt++;
            a.add(randomString());
            if (cnt % 1000 == 0) System.out.println(cnt);
        }
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println(cnt);
    }
}

public static void main(String[] args) {
    new BugShooter();
}
}

异常内容如下:

Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.Random.<init>(Random.java:137)
    at java.util.Random.<init>(Random.java:105)
    at wyf.clonesite.BugShooter.randomString(BugShooter.java:10)
    at wyf.clonesite.BugShooter.<init>(BugShooter.java:23)
    at wyf.clonesite.BugShooter.main(BugShooter.java:33)

GC提前抛出异常看的是内存使用的“速度”,而内存溢出看的是内存使用的“路程”。速度如果太快,GC就认为很危险。

实际中,我们可能确实会有大量开辟不可回收对象的场景,那么如何解决这个问题呢?

  • 下策:关闭GC提前抛出异常的机制,使用-XX:-UseGCOverheadLimit
  • 中策:捕捉此异常,在内存耗尽之前dump程序状态,备份好数据,坦然“就义”
  • 上策:加大堆空间-Xmx1024m

参考资料

https://blog.csdn.net/renfufei/article/details/77585294
https://blog.csdn.net/evilcry2012/article/details/79063822

猜你喜欢

转载自www.cnblogs.com/weiyinfu/p/9815410.html