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
参考: