G1GC OutOfMemory 过早

G1GC OutOfMemory too early

我的测试代码:

int SIZE = 1900;
int[][] array = new int[SIZE][];
for (int i = 0; i < SIZE; i++) {
  array[i] = new int[1024 * 1024 / 4]; // 1MB
  Thread.sleep(10);
  if (i % 100 == 0 && i != 0) {
    System.out.println(i + "Mb added");
  }
}

我用 java 8 -Xmx2048m -XX:+UseG1GC -XX:+PrintGCDetails

中的参数启动它

当仅消耗 1G 时,它会因 OutOfMemory 而失败。

Heap
garbage-first heap   total 2097152K, used 1048100K [0x0000000080000000, 0x0000000080104000, 0x0000000100000000)
region size 1024K, 1 young (1024K), 0 survivors (0K)
Metaspace       used 3273K, capacity 4496K, committed 4864K, reserved 1056768K
class space    used 358K, capacity 388K, committed 512K, reserved 1048576K

我看到 G1 分配的大小是 2G,我想 JVM 正在尝试分配更多,但因 OOM 而失败。但是,如果有一半内存是空闲的,为什么它还要尝试分配更多?

使用 UseConcMarkSweepGC 它工作正常并且数组已满。

我很确定这是由于 Humongous Allocations
如果你添加这个选项

-XX:+PrintAdaptiveSizePolicy

您将能够看到大部分分配都是 1048592 字节,这既不适合单个 G1 区域的 50% 甚至 100%(在输出中看到的是 1024K=1048576 字节)。我假设这意味着每个数组至少占据两个区域。由于这是一个巨大的分配,因此第二个区域中的大部分空闲 space 都无法使用。这很快会导致极端的堆碎片,使进一步的分配变得不可能。

同意@yegodm。解决方案是使用 -XX:G1HeapRegionSize 增加堆区域,以确保以前的 Humongous 对象不再是 Humongous,并将遵循常规分配路径。在此处阅读有关巨大对象分配的更多信息1