首页 > 解决方案 > 外部进程能否强制 JVM 抛出“java.lang.OutOfMemoryError:GC 开销限制超出”

问题描述

在相同的操作系统和硬件上运行的另一个进程(java oder not)是否有可能触发一个

java.lang.OutOfMemoryError: GC overhead limit exceeded

通过消耗 RAM 和/或大量 CPU 负载 - 还是通过其他方式?


来自Java 8 文档

详细消息“超出 GC 开销限制”表明垃圾收集器一直在运行,Java 程序进展缓慢。在垃圾收集之后,如果 Java 进程花费大约 98% 以上的时间进行垃圾收集,并且如果它正在恢复不到 2% 的堆......

这个有点旧的线程我知道这是时间敏感的。然而,这 98% 所指的内容似乎缺乏适当的规范。

编辑 20201008:添加了垃圾收集器人体工程学的链接

标签: javajvmout-of-memory

解决方案


是的,但这在现实生活中不太可能发生。

要让 JVM 抛出java.lang.OutOfMemoryError: GC overhead limit exceeded,必须满足两个条件:

  1. 一个 GC 循环回收的堆空间少于GCHeapFreeLimit(2%);
  2. JVM 花费超过GCTimeLimit(98%) 的时间进行 GC。

外部进程几乎不会影响第一个条件,除非它直接与目标应用程序交互。这意味着,JVM 应该已经处于“几乎内存不足”状态,错误才会发生。

另一个过程可能会影响的是时间。如果此进程大量使用共享 CPU 资源,它可能会通过与 JVM 竞争 CPU 时间来使 GC 运行速度变慢。较慢的 GC 意味着更长的 GC 周期,因此花费在 GC 上的时间百分比更高。

当另一个进程使 JVM throw 时,我能够创建一个人工示例GC overhead limit exceeded,但这真的很棘手。

考虑以下 Java 程序。

import java.util.ArrayList;

public class GCOverheadLimit {
    static ArrayList<Object> garbage = new ArrayList<>();
    static byte[] reserve = new byte[100_000];

    static void fillHeap() {
        try {
            while (true) {
                garbage.add(new byte[10_000]);
            }
        } catch (OutOfMemoryError e) {
            reserve = null;
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("Filling heap");
        fillHeap();

        System.out.println("Starting GC loop");
        while (true) {
            garbage.add(new byte[10_000]);
            garbage.remove(garbage.size() - 1);
            Thread.sleep(20);
        }
    }
}

首先,它用不可回收的对象填充整个堆,留下少量的可用内存。然后在反复分配可回收垃圾使GC一次又一次地发生。迭代之间有一个小的延迟,以保持总 GC 开销低于 98%。

实验使用 1GB 堆和 Parallel GC:

java -Xmx1g -Xms1g -XX:+UseParallelGC GCOverheadLimit

我在具有 CPU 配额的 cgroup 中运行该程序。我的机器有 4 个内核,但我让 JVM 每 100 毫秒周期只使用 200 毫秒 CPU 时间。

mkdir /sys/fs/cgroup/cpu/test
echo 200000 > /sys/fs/cgroup/cpu/test/cpu.cfs_quota_us
echo $JAVA_PID > /sys/fs/cgroup/cpu/test/cgroup.procs

到目前为止,该程序运行良好。现在我在同一个 cgroup 中运行一两个 CPU 烧录进程:

sha1sum /dev/zero &
echo $! > /sys/fs/cgroup/cpu/test/cgroup.procs

由于超出配额,操作系统开始限制进程。GC 次数增加,JVM 最终抛出java.lang.OutOfMemoryError: GC overhead limit exceeded.

注意:重现问题需要仔细选择参数(堆大小、延迟、配额)。其他机器和其他环境的参数会有所不同。我的观点是——这个问题在理论上是可能的,但在实践中可能永远不会发生,因为有太多的因素需要匹配在一起。


推荐阅读