详解 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误!

java 中有几个难兄难弟,比如我昨天写的 java.lang.OutOfMemoryError: Java heap space 和今天要写的 java.lang.OutOfMemoryError: GC overhead limit exceeded 等。要搞清这些知识,就需要深入的理解 JVM 底层原理和实现机制。

那么我们今天就具体来说说 java.lang.OutOfMemoryError: GC overhead limit exceeded 吧!

我们都知道,Java 语言的一大优势就是内存管理,也就是垃圾回收机制。相比其他语言,如:C++等,它们这些语言中并没有自动内存回收机制, 需要程序员手工编写代码来进行内存分配和释放, 以重复利用堆内存。

也正是因为 Java 语言有自动垃圾回收机制,所以一些程序员在使用不当的情况下,会发生一系列你意想不到的 OutOfMemoryError。关于 OutOfMemoryError 错误,目前一共有 8 种,我们一一的来搞定它们!

java.lang.OutOfMemoryError: GC overhead limit exceeded 这种情况发生的原因是程序基本上耗尽了所有的可用内存, GC 也清理不了。

更准确的说法应该是:执行垃圾收集的时间比例太大,有效的运算量太小。默认情况下,如果GC花费的时间超过 98%,并且GC 回收的内存少于 2%,JVM 就会抛出这个错误。

Time used by GC pauses

java.lang.OutOfMemoryError: GC overhead limit exceeded 错误只在连续多次 GC 都只回收了不到2%的极端情况下才会抛出。假如不抛出 GC overhead limit 错误会发生什么情况呢? 那就是 GC 清理的这么点内存很快会再次填满,迫使 GC 再次执行。这样就形成恶性循环,CPU 使用率一直是 100%,而 GC 却没有任何成果。系统用户就会看到系统卡死。以前只需要几毫秒的操作,现在需要好几分钟才能完成。

下面我们来看一个产生“GC overhead limit exceeded” 错误的例子:

package com.xttblog.rtime;
import java.util.Map;
import java.util.Random;
public class TestWrapper {
    public static void main(String args[]) throws Exception {
        Map map = System.getProperties();
        Random r = new Random();
        while (true) {
            map.put(r.nextInt(), "value");
        }
    }
}

为了能够更快的看到效果,我们可以设置一下 JVM 的参数:

java -Xmx12m -XX:+UseParallelGC TestWrapper

需要注意的是,不同的 GC 算法。产生的错误信息也不相同,有的可能会产生 java.lang.OutOfMemoryError: Java heap space 错误。

关于具体的 GC 算法,我给大家一张关于 Java8 GC 算法的列表,我后面再具体的写文章来讲。

Java8 GC 算法

我们在说说上面产生 OutOfMemoryError 错误的代码吧。Map 在进行 rehash 时抛出了 java.lang.OutOfMemoryError 错误消息。如果使用其他垃圾收集算法,比如 -XX:+UseConcMarkSweepGC,或者 -XX:+UseG1GC,错误将被默认的 exception handler 所捕获,但是没有 stacktrace 信息,因为在创建 Exception 时没办法填充 stacktrace 信息。

再比如,有些厂商的 JVM 在 Win7x64,Java8 环境配置如下 UseG1GC:

java -Xmx12m -XX:+UseG1GC TestWrapper

结果产生的错误信息却是:

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

上面这些真实的案例表明,在资源受限的情况下,无法准确预测程序会死于哪种具体的原因。所以在这类错误面前,不能绑死某种特定的错误处理顺序。

有的人在解决 “java.lang.OutOfMemoryError: GC overhead limit exceeded” 错误时,配置了下面的启动参数:

// 不推荐
-XX:-UseGCOverheadLimit

我告诉你,这是一种完全错误的做法。因为 UseGCOverheadLimit 这样使用并不能真正地解决问题,只能推迟一点 out of memory 错误发生的时间,到最后还得进行其他处理。指定这个选项,会将原来的 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误掩盖,变成更常见的 java.lang.OutOfMemoryError: Java heap space 错误消息。

有时候触发 GC overhead limit 错误的原因, 是因为分配给JVM的堆内存不足。这种情况下只需要增加堆内存大小即可。

在大多数情况下, 增加堆内存并不能解决问题。例如程序中存在内存泄漏, 增加堆内存只能推迟产生 java.lang.OutOfMemoryError: Java heap space 错误的时间。

所以,要想从根本上解决问题,则需要排查内存分配相关的代码。简单来说,需要搞清楚一下两点:

  • 哪类对象占用了最多内存?
  • 这些对象是在哪部分代码中分配的?

解决这类问题的流程和我上篇文章《为什么会产生 java.lang.OutOfMemoryError: Java heap space 错误以及如何解决?》中提到的流程一致。我就不再啰嗦了!

参考资料

  • https://plumbr.eu/outofmemoryerror/gc-overhead-limit-exceeded

详解 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误!

: » 详解 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误!

原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/251918.html

(0)
上一篇 2022年5月3日
下一篇 2022年5月3日

相关推荐

发表回复

登录后才能评论