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

8

我有一些短期应用程序,通常(但不总是)不需要任何GC(适合在堆中,通过未引起OOM的epsilon GC证明)。

有趣的是,即使仍有大量的堆可用,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
[...]

让我想知道为什么垃圾回收器在这里运行,因为堆只有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 

问题:
我如何调整 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());
    }
}

该应用程序使用约260MB堆并运行不到500ms。
当使用以下jvm参数启动时:
-Xms1g -Xmx2g -XX:+PrintGCDetails -Xlog:gc + cpu = info -Xlog:gc + heap + exit
您将得到约5-6次GC运行(在java 11 + 16热点vm中测试)。
GC Epsilon测试清楚地表明它可以在不进行任何GC的情况下运行。
挑战:
您能否找到强制G1在此处不进行任何GC的jvm参数?


1
JDK 11 配备了Epsilon GC,它是一个什么也不做的垃圾收集器。如果您确定您的应用程序不会超过内存限制,可以尝试使用它。 - M. Prokhorov
1
@M.Prokhorov 我已经这样做了(请看第一句话)。然而,正如我在那里提到的,它并不总是适用,所以我需要一个GC,但是它要运行得非常晚(当堆几乎满时)。 - MRalwasser
2
是的,抱歉,我错过了那部分。那就算了。 - M. Prokhorov
1
如果您的应用程序寿命较短,您可能想尝试GraalVM的native-image/AOT编译。除此之外,您可能想要使用-client - dan1st
1
你是否遇到了部分垃圾回收(仅清理年轻代)或完全垃圾回收?如果是前者,你可能需要使用“-XX:+ UnlockExperimentalVMOptions -XX:G1NewSizePercent = 45”或类似选项来增加年轻代的大小。 - dan1st
显示剩余11条评论
1个回答

2
您无法完全避免GC,但我们可以暂时远离它。最重要的问题是何时触发GC?
当伊甸园区域已满时,GC会触发Minor GC。这意味着如果我们设置更大的年轻代大小,GC将不会触发,因为年轻代还没有填满。因此另一个问题就来了:如何设置年轻代大小?
JVM有一个-Xmn参数。该参数设置年轻代堆的初始和最大大小。在这个解释中,“initial”是一个重要的关键词。在Oracle文档中,我们可以看到:
-Xmn size 设置分代收集器中年轻代(nursery)堆的初始大小和最大大小(以字节为单位)。附加字母k或K表示千字节,m或M表示兆字节,g或G表示吉字节。堆的年轻代区域用于存放新对象。相对于其他区域,GC在该区域执行得更加频繁。您也可以使用-XX:NewSize设置初始大小,使用-XX:MaxNewSize设置最大大小,而不是使用-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

不要忘记,如果应用程序使用这些参数触发GC(创建对象超过年轻代大小),则可能会出现延迟问题,因为GC可能需要很长时间才能完成。


这是正确的答案。GC被多次调用的原因是JVM以较低的初始内存大小启动,并且每次填满时,它都会调用GC,然后增加内存。如果您从一开始就声明所需的初始内存,则在执行期间不会填充它,因此不需要GC,也不需要扩展堆内存。 - Panagiotis Bougioukos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接