1. G1适用场景
G1从jdk7开始,jdk9被设为默认垃圾收集器;在jdk8就需要指定参数配置,目标就是彻底替换掉CMS。
G1的首要目标是为需要大量内存的系统提供一个保证GC低延迟的解决方案,也就是说堆内存最低在6GB及以上,稳定和可预测的暂停时间小于0.5秒。
如果应用程序具有如下的一个或多个特征,那么将垃圾收集器从CMS或ParallelOldGC切换到G1将会大大提升性能:
(1)Full GC 次数太频繁或者消耗时间太长;
(2)受够了太长的垃圾回收或内存整理时间(0.5-1s);
2. G1垃圾回收方式
2.1 G1的GC类型
1)Ygc:仅处理年轻代region
2)MixedGc:包含所有年轻代以及部分老年代Region。
3)FullGc:全堆扫描,每个Region
2.2 G1的gc原则
G1会在无法分配对象或者巨型对象无法获得连续分区来分配空间时,优先尝试扩展堆空间来获得更多的可用分区。
原则上G1会计算执行GC的时间,并且极力减少花在GC上的时间(包括ygc,mixgc),如果可能,会通过不断扩展堆空间来满足对象分配、转移的需要。
2.3 youngGC(年轻代垃圾回收)
触发:分配一般对象(非巨型对象)时,当所有eden的region使用达到最大阀值并且无法申请足够内存时。
younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。到Old区的标准就是在PLAB中得到的计算结果。因为YoungGC会进行根扫描,所以会stop the world。
2.4 MixGC(混合垃圾回收)
触发:一次YoungGc之后,老年代占据堆内存的百占比超过InitiatingHeapOccupancyPercent(默认45%)时,超过这个值就会触发mixedGC。
混合回收都是基于复制算法进行的,把要回收的Region区存活的对象放入其他Region,然后这个Region全部清理掉,这样就会不断空出来新的Region;
有一个参数-XX:G1HeapWastePercent,默认值5%,即空出来的区域大于整个堆的5%,就会立即停止混合回收了。如正常默认回收次数是8次,但是可能到了4次,空闲Region大于整个堆的5%,就不会再进行后续回收了。
2.5 FullGc(全局垃圾回收)
G1在对象复制/转移失败或者没法分配足够内存(比如巨型对象没有足够的连续分区分配)时,会触发FullGC。开始版本FullGC使用的是stop the world的单线程的Serial Old模式。
JDK10以后,Full GC已经是并行运行,在很多场景下,其表现还略优于 Parallel GC 的并行 Full GC 实现。但是仍然要避免fgc。
3. 最佳实践指导
3.1 不要设置年轻代的大小
通过 -Xmn 显式地指定了年轻代的大小, 会干扰到 G1收集器的默认行为:
(1)G1在垃圾收集时将不再关心暂停时间指标. 所以从本质上说,设置年轻代的大小将禁用暂停时间目标;
(2)G1在必要时也不能够增加或者缩小年轻代的空间. 因为大小是固定的,所以对更改大小无能为力;
3.2 设置 XX:MaxGCPauseMillis=<N>
其值不应该使用平均响应时间,应该考虑使用目标时间的90%或者更小作为响应时间指标. 即90%的用户(客户端)请求响应时间不会超过预设的目标值;
3.3 转移失败
survivors 或 promoted objects 进行GC时如果JVM的heap区不足就会发生提升失败(promotion failure). 堆内存不能继续扩充,因为已经达到最大值了。当使用 -XX:+PrintGCDetails 时将会在GC日志中显示 to-space overflow (to-空间溢出)。该操作很昂贵,原因如下:
1)GC仍继续所以空间必须被释放.
2)拷贝失败的对象必须被放到正确的位置(tenured in place).
3)CSet指向区域中的任何 RSets 更新都必须重新生成(regenerated).
避免转移失败的方法:
1)增加保留内存大小, 其默认值是 10;G1保留内存大小,非必须不会使用保留内存;即增大-XX:G1ReservePercent=n
2)更早启动标记周期(marking cycle).即InitiatingHeapOccupancyPercent设置的小一点??
3)增加标记线程(marking threads)的数量. 合理设置-XX:ConcGCThreads=n
3.4 新生代优化-避免短生命对象进入老年代
预估每次Minor GC后存活下来对象的大小,合理的设置Survivor区,同时考虑高峰期间时,动态年龄判断条件的影响,不要让这种短生命周期对象侥幸逃脱进入老年代
3.5 老年代
系统的停顿时间时关键!是核心,要预测停顿时间,并不是越小越好,过小则回收效果不大
即-XX:MaxGCPauseMills参数优化
这个参数是核心点!如果参数设置的值很大,导致系统运行很久,新生代可能都占用了堆内存的60%了,此时才触发新生代GC,那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象(或是动态年龄判定规则),就会进入老年代中。
如果参数设置过小,即使GC停顿时间很短,但GC频率太大,比如说30秒触发一次新生代gc,每次就停顿30毫秒,这样也是很影响系统性能的。或者GC频率过高,也可能会导致对象很容易就进入了老年代,这样也是有问题;
4. G1相关的参数
4.1 参数说明1
-XX:MaxGCPauseMillis=200 - 设置最大GC停顿时间指标,JVM会尽力实现,但不保证. 默认值为200毫秒.
-XX:InitiatingHeapOccupancyPercent=45 - 如果老年代占据了堆内存的45%的时候,此时会触发一次mixGc。值为0则表示“一直执行GC循环)'. 默认值为45。
-XX:G1MixedGCLiveThresholdPercent:默认值是85%,确定要回收的Region的时候,必须是存活对象低于85%的Region才可以回收。
-XX:G1ReservePercent=n 设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:ConcGCThreads=n 并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:+UseG1GC - 让 JVM使用G1垃圾收集器, jdk9被设为默认垃圾收集器;所以如果你的版本比较新则不再需要使用该参数
-XX:MetaspaceSize=256M 元空间,默认20M,确实有点小。
-XX:MaxMetaspaceSize=512M 最大元空间
下面参数不建议修改
-XX:G1NewSizePercent=5 设置年轻代占整个堆的最小百分比,默认值是堆的5%。需要开启-XX:UnlockExperimentalVMOptions
-XX:G1MaxNewSizePercent=60 设置年轻代占整个堆的最大百分比,默认值是堆的60%。
-XX:NewRatio=n 新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n eden/survivor 空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n 年轻代提升到年老代的最大临界值. 默认值为 15.
-XX:G1HeapRegionSize=n region大小 默认值将根据 heap size 算出最优解;1M-32M
-XX:G1MixedGCCountTarget mixed回收执行次数,默认回收次数8。
-XX:G1HeapWastePercent,默认值是5%,就是说空出来的区域大于整个堆的5%,即使未达到回收次数,也会立即停止混合回收了。
如:默认回收次数是8次,但是可能到了4次,发现空闲Region大于整个堆的5%,就不会再进行后续回收了。
可参考官网说明:https://docs.oracle.com/en/java/javase/13/docs/specs/man/java.html
查看系统默认参数配置,如G1ReservePercent: java -XX:+PrintFlagsFinal | grep G1ReservePercent
JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;
JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
4.2 参数说明2
#堆内存最大最小值为4g,新生代内存2g
-Xms4g -Xmx4g -Xmn2g
#元空间128m,最大320m
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=320m
#开启远程debug
-Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n
#使用G1垃圾收集器,在低延迟和高吞吐间寻找平衡,可以调整最大停止时间,设置新生代大小来提高吞吐量,让出cpu资源
-XX:+UseG1GC
#设置最大暂停时间,默认200ms
-XX:MaxGCPauseMillis=200
#指定Region大小,必须是2次幂
-XX:G1HeapRegionSize=2m
#反复执行混合回收8次,每次回收受MaxGCPauseMillis的影响可能一次性回收不了所有垃圾,增加次数才能回收的更彻底
-XX:G1MixedGCCountTarget=8
# 混合回收整理出来的空闲空间占heap的10时,结果老年代的回收,默认5
-XX:G1HeapWastePercent=10
#设置新生代大小,最大60%,默认5%
-XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=50
-XX:SurvivorRatio=8
#在控制台输出GC情况
-verbose:gc
#gc日志打印到执行日志文件
-Xloggc:./logs/job_execute_gc.log
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime
#可以生成更详细的Survivor空间占用日志
-XX:+PrintAdaptiveSizePolicy
#jdk 1.6开始,默认server模式下开启了这个参数,意为当jvm检测到程序在重复抛一个异常,在执行若干次后会将异常吞掉
-XX:-OmitStackTraceInFastThrow
-XX:-UseLargePages
#指定加载配置文件
--spring.config.location=classpath:/,classpath:/config/,file:./,file:./config/,file:/home/mall-job/conf/
#---当前分布式任务调度采用jvm参数,-Xmn2g,-XX:MaxGCPauseMillis=400调整新生代内存大小,增大暂停时间提高吞吐量---------------------------
-Xms4g -Xmx4g -Xmn2g -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n -XX:+UseG1GC -XX:MaxGCPauseMillis=400 -XX:G1HeapRegionSize=2m -XX:G1MixedGCCountTarget=8 -XX:G1MixedGCCountTarget=8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:/logs/execute/mall-job-execute-gc.log
5. 垃圾回收日志记录的垃圾收集原因
5.1 G1 Evacuation Pause
2022-05-17T02:59:17.637+0800: 34453.382: [GC pause (G1 Evacuation Pause) (young), 0.0521838 secs]
[Parallel Time: 48.1 ms, GC Workers: 10]
[GC Worker Start (ms): Min: 34453382.6, Avg: 34453382.8, Max: 34453383.0, Diff: 0.3]
[Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 2.0, Diff: 1.6, Sum: 7.9]
[Update RS (ms): Min: 21.9, Avg: 22.9, Max: 23.4, Diff: 1.6, Sum: 229.2]
[Processed Buffers: Min: 43, Avg: 51.5, Max: 77, Diff: 34, Sum: 515]
[Scan RS (ms): Min: 17.4, Avg: 17.6, Max: 17.8, Diff: 0.4, Sum: 176.5]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Object Copy (ms): Min: 5.9, Avg: 6.0, Max: 6.1, Diff: 0.2, Sum: 60.3]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[Termination Attempts: Min: 1, Avg: 14.1, Max: 21, Diff: 20, Sum: 141]
[GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 1.4]
[GC Worker Total (ms): Min: 47.3, Avg: 47.5, Max: 47.8, Diff: 0.5, Sum: 475.4]
[GC Worker End (ms): Min: 34453430.3, Avg: 34453430.4, Max: 34453430.5, Diff: 0.3]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 1.0 ms]
[Other: 3.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.8 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 1.1 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.8 ms]
[Eden: 2128.0M(2128.0M)->0.0B(2128.0M) Survivors: 6144.0K->6144.0K Heap: 3265.8M(4096.0M)->1138.3M(4096.0M)]
[Times: user=0.49 sys=0.00, real=0.05 secs]
5.2 to-space exhausted(空间耗尽)
2022-05-16T18:39:14.581+0800: 4450.326: [GC pause (G1 Evacuation Pause) (young) (to-space exhausted), 0.0626620 secs]
[Parallel Time: 24.5 ms, GC Workers: 10]
[GC Worker Start (ms): Min: 4450326.1, Avg: 4450326.1, Max: 4450326.2, Diff: 0.1]
[Ext Root Scanning (ms): Min: 0.9, Avg: 1.1, Max: 2.0, Diff: 1.1, Sum: 11.4]
[Update RS (ms): Min: 1.3, Avg: 1.9, Max: 2.1, Diff: 0.9, Sum: 19.2]
[Processed Buffers: Min: 16, Avg: 33.3, Max: 71, Diff: 55, Sum: 333]
[Scan RS (ms): Min: 0.1, Avg: 0.1, Max: 0.2, Diff: 0.1, Sum: 1.3]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 20.6, Avg: 20.7, Max: 20.8, Diff: 0.2, Sum: 207.0]
[Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 1.3]
[Termination Attempts: Min: 1, Avg: 1.3, Max: 3, Diff: 2, Sum: 13]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]
[GC Worker Total (ms): Min: 24.0, Avg: 24.1, Max: 24.1, Diff: 0.1, Sum: 240.5]
[GC Worker End (ms): Min: 4450350.2, Avg: 4450350.2, Max: 4450350.2, Diff: 0.1]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.5 ms]
[Other: 37.6 ms]
[Evacuation Failure: 35.3 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.5 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.6 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.6 ms]
[Eden: 1618.0M(1618.0M)->0.0B(1620.0M) Survivors: 20.0M->18.0M Heap: 4075.5M(4096.0M)->2632.0M(4096.0M)]
[Times: user=0.47 sys=0.00, real=0.07 secs]
old区的使用速度超过了垃圾收集器的回收速度,考虑两种调优的思路。
通过调小-XX:InitiatingHeapOccupancyPercent=N这个参数,默认情况下该参数是45,让G1更早得启动混合式垃圾收集周期。
增加每次混合式垃圾收集收集的Old分区数量,通过调整-XX:G1MixedGCCountTarget=N参数可以控制每个混合式周期中回收的Old分区数量,该参数的默认值是8;
增加-XX:G1ReservePercent的大小,在G1中这个默认值是10%。
通过调整-XX:ConcGCThreads
,增加用于垃圾收集的线程个数,代价是会多一些CPU的消耗;也就是会占用Java应用的CPU时间,从垃圾回收日志,可以看出是几个线程进行垃圾回收。
有时候转移失败是由于survivor分区中没有足够的空间容纳新晋升的对象,如果是这种情况,还可以考虑增加-XX:G1ReservePercent
的大小,在G1中这个默认值是10%。
-XX:G1HeapRegionSize=4M,分区太小,容易引起碎片,并且容易出现回收效率低。
-XX:MaxTenuringThreshold=n:晋升到老年代的“年龄”阀值,默认值为 15。
-XX:G1MixedGCCountTarget:一次全局并发标记之后,后续最多执行的MixedGC次数。 默认值是8
5.3 to-space overflow(空间溢出)
增加-XX:G1ReservePercent。默认值是Java堆的10%。
5.4 Humongous Allocation
可以加大region的大小,设置-XX:G1HeapRegionSize=n。
6. 垃圾收集日志相关参数
6.1 -XX:+HeapDumpAfterFullGC 和 -XX:+HeapDumpOnOutOfMemoryError
在发生FGC和OOM的时候将当时的Java堆情况记录下来,用于事后分析。
6.2 日志记录参数
-Xms6144m -Xmx6144m -XX:+UseG1GC -XX:MaxGCPauseMillis=500
-Xloggc:/home/finance/Logs/xxx/xxx-gc.log
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=50M
7. 参数配置案例
nohup java -Xms2048m -Xmx2048m -XX:+UseG
1GC -XX:G1ReservePercent=10 -XX:G1MixedGCCountTarget=8 -XX:InitiatingHeapOccup
ancyPercent=45 -XX:MaxMetaspaceSize=512m -XX:MaxTenuringThreshold=15 -XX:G1HeapR
egionSize=4m -XX:+HeapDumpAfterFullGC -XX:+HeapDumpOnOutOfMemoryError -XX:+Unloc
kExperimentalVMOptions -XX:G1NewSizePercent=40 -XX:G1MaxNewSizePercent=60 -XX:G1
MixedGCLiveThresholdPercent=50 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+P
rintGCDateStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFi
leSize=50M -Xloggc:./gc.log -jar server.jar > server.log 2>&1 &