短暂的 Java 应用程序:如何调整 G1 以稍后启动?

MRa*_*ser 8 java performance garbage-collection jvm g1gc

我有一些短期应用程序,通常(但并非总是)不需要任何 GC(适合堆,epsilon GC 通过不导致 OOM 证明了这一点)。

有趣的是,尽管仍然有大量空闲堆,但G1 仍然很早就开始启动:

[0.868s][info   ][gc,start     ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[0.869s][info   ][gc,task      ] GC(0) Using 13 workers of 13 for evacuation
[0.872s][info   ][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.0ms
[0.873s][info   ][gc,phases    ] GC(0)   Evacuate Collection Set: 2.8ms
[0.873s][info   ][gc,phases    ] GC(0)   Post Evacuate Collection Set: 0.4ms
[0.873s][info   ][gc,phases    ] GC(0)   Other: 1.0ms
[0.873s][info   ][gc,heap      ] GC(0) Eden regions: 51->0(45)
[0.873s][info   ][gc,heap      ] GC(0) Survivor regions: 0->7(7)
[0.873s][info   ][gc,heap      ] GC(0) Old regions: 0->2
[0.873s][info   ][gc,heap      ] GC(0) Humongous regions: 4->2
[0.873s][info   ][gc,metaspace ] GC(0) Metaspace: 15608K->15608K(1062912K)
[0.874s][info   ][gc           ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 55M->10M(1024M) 5.582ms
[0.874s][info   ][gc,cpu       ] GC(0) User=0.00s Sys=0.00s Real=0.01s
[...]
Run Code Online (Sandbox Code Playgroud)

这让我想知道为什么 GC 会在这里运行,因为堆只有 55MB。
总的来说,我通常会运行 10-15 次 GC,这些运行总计消耗的用户 cpu 时间约为 1 秒,我想避免这种情况。

JVM: openjdk version "11.0.16" 2022-07-19
JVM ARGS: -Xms1g -Xmx2g -XX:+PrintGCDetails -Xlog:gc+cpu=info -Xlog:gc+heap+exit 
Run Code Online (Sandbox Code Playgroud)

问题:
如何调整 G1 (jdk 11) 以尽可能晚地启动(例如,当堆/伊甸园已满 90% 时),以在大多数情况下理想地避免任何 GC 暂停/运行?
增加-XX:InitiatingHeapOccupancyPercent(例如增加到 90%)对我的情况没有帮助。


编辑:

通过在您的 jvm 上执行这个 java 类来亲自尝试一下:

public class GCTest {
    public static void main(String[] args) {

        java.util.Map<String,byte[]> map = new java.util.HashMap<>();
        
        for(int i=0;i<1_000_000;i++)
            map.put(i+"", new byte[i % 256]);   
        
        System.out.println(map.size());
    }
}
Run Code Online (Sandbox Code Playgroud)

该应用程序消耗约 260MB 堆,运行时间不到 500 毫秒。
当使用以下 jvm 参数启动时:
-Xms1g -Xmx2g -XX:+PrintGCDetails -Xlog:gc+cpu=info -Xlog:gc+heap+exit
您将获得约 5-6 次 GC 运行(使用 java 11+16 热点虚拟机进行测试)。
GC Epsilon 测试清楚地表明它可以在没有任何 GC 的情况下运行。

挑战:
你能找到强制 G1 不在这里进行任何 GC 的 jvm 参数吗?

Tur*_*rac 2

你无法逃避GC,但我们可以暂时离开。最重要的问题是GC什么时候触发?

当Eden大小满时,GC将触发minor GC。这意味着如果我们设置更大的年轻代大小,GC将不会触发,因为年轻代尚未满。那么另一个问题又来了。我们如何设置年轻集合的大小?

JVM 有-Xmn参数。该参数设置年轻代堆的初始大小和最大大小。初始是本说明中的重要关键字。在 Oracle 文档中

-Xmn size 设置分代收集器中年轻代(托儿所)堆的初始和最大大小(以字节为单位)。附加字母 k 或 K 表示千字节,m 或 M 表示兆字节,或 g 或 G 表示千兆字节。堆的年轻代区域用于新对象。GC 在该区域中执行的频率高于其他区域。您可以使用 -XX:NewSize 设置初始大小,使用 -XX:MaxNewSize 设置最大大小,而不是使用 -Xmn 选项来设置年轻代堆的初始大小和最大大小。

因此,当应用 -Xmn 参数时,-Xmn300m -Xmx512m -XX:+PrintGCDetails -Xlog:gc+cpu=info -Xlog:gc+heap+exit我们不会看到 GC 暂停。我试过你的例子。结果:

[0.002s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
[0.007s][info   ][gc,heap] Heap region size: 1M
[0.015s][info   ][gc     ] Using G1
[0.015s][info   ][gc,heap,coops] Heap address: 0x00000000e0000000, size: 512 MB, Compressed Oops mode: 32-bit
1000000
[0.321s][info   ][gc,heap,exit ] Heap
[0.321s][info   ][gc,heap,exit ]  garbage-first heap   total 522240K, used 245760K [0x00000000e0000000, 0x0000000100000000)
[0.321s][info   ][gc,heap,exit ]   region size 1024K, 221 young (226304K), 0 survivors (0K)
[0.321s][info   ][gc,heap,exit ]  Metaspace       used 6722K, capacity 6815K, committed 7040K, reserved 1056768K
[0.321s][info   ][gc,heap,exit ]   class space    used 596K, capacity 613K, committed 640K, reserved 1048576K
Run Code Online (Sandbox Code Playgroud)

**不要忘记,如果应用程序使用这些参数触发 GC(创建大于年轻集合大小的对象),您会看到延迟问题,因为 GC 可能需要很长时间才能完成