JVM-GC- 耗时频频升高,这次排查完想说:还有谁,nginx 作用和工作原理
降低 abortable preclean 时间,而且不增加 final remark 的时间(因为 remark 是 STW 的)。
5. JVM 参数调优
5.1 第一次调优
先尝试调低 abortable preclean 阶段的时间,看看效果。
有两个参数可以控制这个阶段何时结束:
-XX:CMSMaxAbortablePrecleanTime=5000 ,默认值 5s,代表该阶段最大的持续时间
-XX:CMSScheduleRemarkEdenPenetration=
50 ,默认值 50%,代表 Eden 区使用比例超过 50%就结束该阶段进入 remark
调整为最大持续时间为 1s,Eden 区使用占比 10%,如下:
-XX:CMSMaxAbortablePrecleanTime=1000
-XX:CMSScheduleRemarkEdenPenetration=10
为什么调整成这样两个值,我们是这样考虑的:首先每次 CMS 都发生在老年代使用占比达到 80%时,因为这是由下面两个参数决定的:
-XX:CMSInitiatingOccupancyFraction=80
-XX:+UseCMSInitiatingOccupancyOnly
而老年代的增长是由于部分对象在 Minor GC 后仍然存活,被晋升到老年代,导致老年代使用占比增长的,也就是在每次 CMS GC 发生之前刚刚发生过一次 Minor GC,所以在那一刻新生代的使用占比是很低的。那么我们预计这个时候尽快结束 abortable preclean 阶段,在 remark 时就不需要扫描太多的 Eden 区对象,remark STW 的时间也就不会太长。
调整的思路是这样了,那到底效果如何呢?
第一次调整的的结果
在统计期间(17 小时左右)内,发生过 2 次 CMS GC。Abortable Preclean 平均耗时 835ms,这是预期内的。但是 Final Remark 平均耗时 495ms(调整前是 112ms),其中一次是 80ms,另一次是 910ms!将近 1 秒钟!Remark 是 STW 的!对于要求低延时的应用来说这是无法接受的!
对比这两次 CMS GC 的详细 GC 日志,我们发现了一些对分析问题非常有用的东西。
remark 耗时 80ms 的那次 GC 日志
[YG occupancy: 181274 K (1887488 K)] - 年轻代当前占用情况和总容量
耗时 80ms 的这次 remark 发生时(早上 9 点,非高峰时段),新生代(YG)占用 181.274M。
remark 耗时 910ms 的那次 GC 日志
[YG occupancy: 773427 K (1887488 K)]
耗时 910ms 的这次 remark 发生时(晚上 10 点左右,高峰时段),新生代(YG)占用 773.427M。因为这个时候高峰期,新生代的占用量上升的非常快,几乎同样的时间内,非高峰时段仅上升到 181M,但是高峰时段就上升到 773M。
这里能得出一个有用的结论:如果 abortale preclean 阶段时间太短,随后在 remark 时,新生代占用越大,则 remark 持续的时间(STW)越长。
这就陷入了两难了,不缩短 abortale preclean 耗时会报 longgc;缩短的话,remark 阶段又会变长,而且是 STW,更不能接受。
对于这种情况,CMS 提供了 CMSScavengeBeforeRemark 参数,尝试在 remark 阶段之前进行一次 Minor GC,以降低新生代的占用。
-XX:+CMSScavengeBeforeRemark
Enables scavenging attempts before the CMS remark step. By default, this option is disabled.
5.2 第二次调优
调优前的考虑:增加-XX:+CMSScavengeBeforeRemark 不是没有代价的,因为这会增加一次 Minor GC 停顿。所以这个方案好或者不好的判断标准就是:增加 CMSScavengeBeforeRemark 参数之后的 minor GC 停顿时间 + remark 停顿时间如果比增加之前的 remark GC 停顿时间要小,这才是好的方案。
第二次调整的结果
在统计期间(20 小时左右)内,发生 3 次 CMS GC。Abortable preclean 平均耗时 693ms。Final remark 平均耗时 50ms,最大耗时 60ms。Final remark 的时间比调优前的平均时间(112ms)更低。
那么 CMS GC 前的 Minor GC 停顿时间又如何呢?来看看详细的 GC 日志。
3 次 CMS GC remark 前的 Minor GC 日志分析
第 1 次是非高峰时段的表现,Minor GC 耗时 0.01s + remark 耗时 0.06s = 0.07s = 70ms,如下
第 2 次是高峰时段,Minor GC 耗时 0.01s + remark 耗时 0.05s = 0.06s = 60ms,如下
第 3 次是非高峰时段,Minor GC 耗时 0.00s + remark 耗时 0.04s = 0.04s = 40ms,如下
所以,3 次 Minor GC + remark 耗时的平均耗时 < 60ms,这比第一次调优时 remark 平均耗时 495ms 好得多了。
6.优化结果
至此,我们最初的目标- 降低 abortable preclean 时间,而且不增加 final remark 的时间 ,已经达到了。甚至 remark 的时间也缩短了。
7. 小结
解决 abortable preclean 时间过长的方案可以归结为两步:
缩短 abortable preclean 时长,通过调整这两个参数:
评论