1.现象
发现某线上服务(jdk1.8、G1算法),old区只在fullgc阶段回收(也就是说没有触发mixed gc)
如下图所示,虽然平时都进行gc,但是old区没有减少,只有fullgc的时候,old区才被回收
线上服务对应的gc参数为:
-XX:InitiatingHeapOccupancyPercent=70 -XX:G1NewSizePercent=40
2.G1
G1核心逻辑:
通过将内存划分若干个Region区域,可以做到gc时只回收部分内存,同时借助多核加速Region回收速度。将停顿时间变得可预测(通过调整回收内存大小,控制gc时间)、避免了内存碎片
old区和young区大小处于动态变化过程中,根据预测算法,结合-XX:MaxGCPauseMillis参数,动态调整两者内存比例,以及mixed gc回收old比例,将gc停顿时间尽可能控制在参数以内
G1中主要存在3种gc算法:
Young GC:选定所有年轻代里的Region。G1是通过调整年轻代大小,控制年轻代Region数量来控制YGC的开销。
Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的部分老年代Region,在用户指定的停顿时间目标范围内尽可能选择收益高的老年代Region回收,G1就是通过控制回收老年代Region数量来控制Mixed GC的开销的。
FullGC:Serial Old GC
只有Mixed gc 和Full gc会回收old区
在G1中有关键的3个参数:
-XX:InitiatingHeapOccupancyPercent:默认值45,超过这个值,开始触发全局标记,进而触发mixed gc,注意这个值表示的是:老年区已使用空间/整个堆空间
-XX:G1NewSizePercent:默认值5%,新生代最小值
-XX:G1MaxNewSizePercent:默认值60%,新生代最大值
3.InitiatingHeapOccupancyPercent之坑
当时线上的gc参数配置如下:
-XX:InitiatingHeapOccupancyPercent=70 -XX:G1NewSizePercent=40
可以发现问题:
XX:G1NewSizePercent = 40 (young区占用40%)导致 old区最多有 60%的空间,无法达到 -XX:InitiatingHeapOccupancyPercent=70,永远触发不了全局标记、mixedgc了,导致老年代回收不了,一直挤压等到fullgc,才能回收old区
相关的问题,也被提给jdk官方:
https://bugs.openjdk.org/browse/JDK-8151176
https://bugs.openjdk.org/browse/JDK-8142484
可以看到这个问题,在高版本的jdk9,已经被修复,不存在这个问题了。但是使用低版本的jdk,还要注意这个坑
4. 验证
我们将gc参数调整为:
-XX:InitiatingHeapOccupancyPercent=40 -XX:G1NewSizePercent=10 -XX:G1MaxNewSizePercent=35
将young区控制在35%以下,上线效果如下,可以看到老年代不在持续增加,会跟随mixed gc回收,不在触发fullgc(会导致较长的STW,以及oom)