mixedGc在G1垃圾回收器永远不会发生

从生产环境得到的结论:开启了g1垃圾回收器,如果使用默认G1配置,则mixedGc永远不会发生。

参考文档openJdk官网讨论该bug的链接stackoverfloa讨论该bug的链接

原因:jdk8的G1垃圾回收器默认参数配置有bug,需要自己调整InitiatingHeapOccupancyPercent参数。

A、G1垃圾回收器的开始mixedGc的阈值InitiatingHeapOccupancyPercent默认是45%,这指的是已经使用内存(老年代+新生代)占整个堆的占比。

B、G1MaxNewSizePercent的默认值是60%

C、G1垃圾回收期与之前的垃圾回收器的回收模型(和内存区域划分)如此不同,所以执行机制也很不同,执行一次ygc后,会计算是否达到InitiatingHeapOccupancyPercent,如果达到,则触发mixedGc。在ygc后再判断的机制,导致实际的计算是:老年代使用/整个堆。

而老年代最多达到100%-60%=40%,所以永远无法触发mixedGc,会在老年代达到97%使用率或者没有连续分配空间时直接触发fullgc。

 

回顾生产事件:

1、生产内存告警后,去生产执行jmap -heap命令,查看堆内内存最高使用率为87%,但尚未达到fullGc条件(jdk8中老年代使用97%会触发fullGc)。加上堆外内存几百M,达到了虚拟机的告警条件。

2、换用G1垃圾回收器,指望InitiatingHeapOccupancyPercent触发mixedGc,来阻止告警。

3、发现依旧告警,一方面了解到InitiatingHeapOccupancyPercent的深坑。

4、另一方面,在同事云神的提示下,开始理不直也气壮了:虽然我们有坑,但我们也有fullGc保底,不会oom,为啥多吃几百M内存,就告警。对比了下别人不告警的机器,机器分配内存都比我们大,计算了下如果同样配置我们也可以不告警。于是和运维同事一起计算后,一致同意帮我们加内存。

 

另外,细节总结如下——

1、我们没有内存泄露:在生产通过jmap -dump:live, format=b,file=<filename> <pid>命令打dump文件,顺便执行一次FullGc,老年代瞬间从15N变为N。腰杆都直了,说话分贝都提升了些。

2、我们所有计算都与内存告警图相符,计算过程:

A、机器内存使用率上升=(此刻堆外内存+堆内使用内存)-(应用启动时堆外内存+堆内使用内存)/机器总内存=老年代增长/机器总内存

B、堆外内存+堆内使用内存=ps aux命令RSS列相加=top命令内存使用

C、堆内使用内存=gc日志中总used=jmap -heap 命令中的G1 heap used=jmap -heap 命令中的老年代使用+新生代使用

D、老年代增长=jmap -heap的old值=此刻ygc高点-应用启动时ygc高点=此刻ygc低点=应用启动时ygc低点

4、分析老年代增加过快,所以告警往往几天就来了。

使用mat工具分析dump文件,意外发现job引用对象在老年代中最大,老年代增长迅速原因总结如下,我们是C导致的:

A、大对象巨型对象,会直接进入老年代
B、GC日志中提升失败:一般是survivor区域配置过小或者新生代配置过小导致的。小概率是ygc频繁,但限定了ygc执行时间于是直接晋升老年代。
C、对象存活过久:一般是守护线程中的一些对象,或者其它场景下对象被长时间引用,ygc次数超过MaxTenuringThreshold,则进入老年代。我们job捞取几百笔数据,等全部处理完才释放,存活过久。
D、内存泄漏:如果内存泄漏,老年代的使用率居高不下,fullGC也无法回收。

 

posted @ 2020-09-18 22:49  叶扬  阅读(2037)  评论(1编辑  收藏  举报