GC问题分析&解决

jdk8中jvm的内存结构:

GC 主要工作在 Heap 区和 MetaSpace 区

关键性能指标(Key Performance Indicators)

延迟(Latency):

  • 可以理解为最大停顿时间,即垃圾收集过程中一次 STW 的最长时间,越短越好,一定程度上可以接受频次的增大,这也是GC 技术的主要发展方向。
    • 通常互联网系统普遍追求的是低延时,避免一次 GC 停顿的时间过长对用户体验造成损失。
    • 一次停顿的时间不超过应用服务的 TP9999 (top percentile 99.99)

吞吐量(Throughput):

  • 吞吐量优先的收集器可以接受较长的停顿


触发STW的Full GC的场景:

  • CMS GC时出现promotion failed和concurrent mode failure
  • 老年代内存分配担保失败:
    • 统计得到的Young GC晋升到老年代的平均大小大于老年代的剩余空间;
  • 主动触发Full GC(执行jmap -histo:live [pid])

常见问题


对象过早晋升

一、现象:

  • 老年代使用率增长过快,每次Full GC后老年代使用率呈现断崖式下跌,Minor GC 和 Full GC都比较频繁。
  • 老年代空间占用量统计(通过gc日志统计):

二、影响:

  • Young GC 频繁,总的吞吐量下降。
  • Full GC 频繁,可能会有较大停顿。

三、原因:

年轻代(主要是eden区)过小: 

  • eden区无法容纳足够多的年轻对象,造成young gc的次数增加。
  • 本应该在年轻代就回收的对象却晋升到了老年代,加快了老年代的占用速度,造成full gc的次数增加。

四、优化:

动作:

  • 增大新生代空间,降低minor gc和major gc的频率,减少系统STW的时间、提高系统的吞吐量。

原理:

  • 对于虚拟机来说,复制对象的成本要远高于扫描成本,所以单次minor GC时间更多取决于GC后存活对象的数量,而非Eden区的大小。
  • 如果堆中短期对象很多,那么 新生代扩容前minor gc时新生代存活对象的数量 与 新生代扩容后minor gc时新生代存活对象的数量 基本相同。
  • 如果堆中短期对象很多,那么新生代扩容后单次minor gc的时间只会略微增加(对象标记的时间略微增加导致),而minor gc的频率却可以大幅下降。
  • minor gc的频率下降,对象在新生代得到充分回收,只有生命周期长的对象才进入老年代,故老年代增速变慢,major gc频率降低。
  • minor gc频率降低会提高系统吞吐量,major gc频率降低会减少系统STW的时间、提高系统的吞吐量。

五、总结:

分区大小应该依赖应用程序中对象生命周期的分布情况:如果应用存在大量的短期对象,应该选择较大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。

​​​​


CMS-remark阶段耗时较长

一、现象:

  • CMS-remark阶段耗时较长

二、影响

  • remark阶段需要Stop the word,故remark阶段耗时较长导致服务一段时间内无响应,系统可用性下降。

三、原因

  • 虽然CMS收集器增加了可中断的预清理阶段(CMS-concurrent-abortable-preclean)用来等待Minor GC的发生,但是该阶段有时间限制,如果超时等不到Minor GC,那么remark阶段需要重新扫描对象的数量就会很多。
  • 单线程去执行重新标记的任务,耗时比多线程并行重新标记花费的时间要多。

四、优化

动作:

  • remark前强制进行一次minor gc
  • remark阶段采用并行标记模式

原理:

  • CMS-remark阶段扫描对象的范围是整个堆(新生代+老年代),堆中对象的数目影响了Remark阶段耗时。
  • 降低Remark阶段耗时问题转换成如何减少新生代对象数量。
  • 新生代中对象的特点是“朝生夕灭”,这样如果remark前执行一次Minor GC,大部分对象就会被回收,那么remark阶段扫描对象的数量就大大降低了。

参数:

-XX:+CMSScavengeBeforeRemark

  • 在remark前强制进行一次minor gc,减少remark标记阶段扫描对象的数量,进而降低remark扫描的时间。

-XX:+CMSParallelRemarkEnabled

  • 可以并行remark,减少暂停的时间。


对象晋升失败

一、场景:

young gc过程中,To Survivor空间不足以放下eden + from Survivor中存活的对象,故这些存活的对象只能尝试着晋升到老年代中,若此时老年代的内存也不足以放下这些对象,则晋升失败(promotion failed),此时,老年代会进行full gc。

优化:

  • 适当地调大Survivor空间,尽量避免朝生夕灭的对象进入老年代。
  • 使用标记整理算法收集,及时整理老年代中的内存碎片,避免因内存碎片太多导致大对象晋升失败:
    • 开启老年代内存压缩:-XX:UseCMSCompactAtFullCollection
    • 每次cms gc时都进行内存压缩:-XX:CMSFullGCBeforeCompaction=0


并发收集失败

一、场景:

  • 场景1:CMS GC期间,业务线程将对象放到老年代,若此时老年代空间不足,则会导致CMS并发收集失败(Concurrent Mode Failure)。
  • 场景2:CMS GC期间,若某次young gc过程中发生了promotion failed,则也会导致CMS并发收集失败。
  • CMS并发收集失败发生后,由于老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,这时jvm会停止所有的应用线程,同时终止CMS收集,直接进行Serial Old来收集。

二、现象:

stop the world持续时间比较长,系统持续(十几秒甚至几分钟)无响应。

三、优化:

  • 在业务低峰时段提前触发full gc。
    • # 没有开启-XX:+DisableExplicitGC的前提下调用System.gc()就会发生FullGC
  • 降低触发CMS GC的阈值,保障老年代有足够的空间。
    • 开启根据阈值触发CMS GC开关:-XX:+UseCMSInitiatingOccupancyOnly
    • 设置触发CMS GC的阈值:-XX:CMSInitiatingOccupancyFraction=xx  (默认是92)


GC调优示例:
你假笨:http://lovestblog.cn/ 

https://blog.csdn.net/flysqrlboy/article/details/88679457


 

参考:

Java中9种常见的CMS GC问题分析与解决

从实际案例聊聊Java应用的GC优化 - 美团技术团队

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值