Can an external process force the JVM to throw a "java.lang.OutOfMemoryError: GC overhead limit exceeded"
Can an external process force the JVM to throw a "java.lang.OutOfMemoryError: GC overhead limit exceeded"
同一操作系统和硬件上的另一个进程(java 或否)运行 是否有可能触发
java.lang.OutOfMemoryError: GC overhead limit exceeded
通过消耗 RAM and/or 大量 CPU 负载 - 或者通过其他方式?
The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap...
和这个 somewhat older thread 我知道这是时效性的。然而,它似乎缺乏对这 98% 所指内容的适当说明。
编辑 20201008: 添加了 Link to the Garbage Collector Ergonomics
是的,但在现实生活中这不太可能。
要让 JVM 抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded
,必须满足两个条件:
- 一个 GC 循环回收少于
GCHeapFreeLimit
(2%) 堆 space;
- JVM 花在 GC 上的时间超过
GCTimeLimit
(98%)。
外部进程几乎不会影响第一个条件,除非它直接与目标应用程序交互。这意味着,JVM 应该已经处于“几乎内存不足”状态才能发生错误。
另一个进程可能会影响时间。如果此进程大量使用共享 CPU 资源,它可以通过与 JVM 竞争 CPU 时间来使 GC 运行 变慢。较慢的 GC 意味着更长的 GC 周期,因此花费在 GC 上的时间百分比更多。
当另一个进程使 JVM 抛出 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);
}
}
}
首先,它用 non-reclaimable 个对象填充整个堆,留下少量可用内存。然后在反复分配可回收的垃圾,让GC一次又一次的发生。迭代之间有一个小的延迟,以保持总 GC 开销低于 98%。
实验使用 1GB 堆和并行 GC:
java -Xmx1g -Xms1g -XX:+UseParallelGC GCOverheadLimit
我 运行 这个程序在 cgroup 中,配额 CPU。我的机器有 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
到目前为止程序运行良好。现在我 运行 一两个 CPU 同一个 cgroup 中的刻录进程:
sha1sum /dev/zero &
echo $! > /sys/fs/cgroup/cpu/test/cgroup.procs
由于超出配额,OS 开始限制进程。 GC次数增加,JVM最终抛出java.lang.OutOfMemoryError: GC overhead limit exceeded
.
注意:重现问题需要仔细选择参数(堆大小、延迟、配额)。其他机器和其他环境的参数会有所不同。我的观点是-这个问题理论上是可能的,但在实践中可能永远不会发生,因为有太多的因素需要一起匹配。
同一操作系统和硬件上的另一个进程(java 或否)运行 是否有可能触发
java.lang.OutOfMemoryError: GC overhead limit exceeded
通过消耗 RAM and/or 大量 CPU 负载 - 或者通过其他方式?
The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap...
和这个 somewhat older thread 我知道这是时效性的。然而,它似乎缺乏对这 98% 所指内容的适当说明。
编辑 20201008: 添加了 Link to the Garbage Collector Ergonomics
是的,但在现实生活中这不太可能。
要让 JVM 抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded
,必须满足两个条件:
- 一个 GC 循环回收少于
GCHeapFreeLimit
(2%) 堆 space; - JVM 花在 GC 上的时间超过
GCTimeLimit
(98%)。
外部进程几乎不会影响第一个条件,除非它直接与目标应用程序交互。这意味着,JVM 应该已经处于“几乎内存不足”状态才能发生错误。
另一个进程可能会影响时间。如果此进程大量使用共享 CPU 资源,它可以通过与 JVM 竞争 CPU 时间来使 GC 运行 变慢。较慢的 GC 意味着更长的 GC 周期,因此花费在 GC 上的时间百分比更多。
当另一个进程使 JVM 抛出 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);
}
}
}
首先,它用 non-reclaimable 个对象填充整个堆,留下少量可用内存。然后在反复分配可回收的垃圾,让GC一次又一次的发生。迭代之间有一个小的延迟,以保持总 GC 开销低于 98%。
实验使用 1GB 堆和并行 GC:
java -Xmx1g -Xms1g -XX:+UseParallelGC GCOverheadLimit
我 运行 这个程序在 cgroup 中,配额 CPU。我的机器有 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
到目前为止程序运行良好。现在我 运行 一两个 CPU 同一个 cgroup 中的刻录进程:
sha1sum /dev/zero &
echo $! > /sys/fs/cgroup/cpu/test/cgroup.procs
由于超出配额,OS 开始限制进程。 GC次数增加,JVM最终抛出java.lang.OutOfMemoryError: GC overhead limit exceeded
.
注意:重现问题需要仔细选择参数(堆大小、延迟、配额)。其他机器和其他环境的参数会有所不同。我的观点是-这个问题理论上是可能的,但在实践中可能永远不会发生,因为有太多的因素需要一起匹配。