上篇文章介绍用户线程与GC线程并发执行时可能产生的问题以及使用三色标记法演示原始快照和增量更新两种解决方案
这篇文章将主要介绍并发垃圾收集器中的CMS,其中CMS使用增量更新来解决对象消失问题,如果不了解增量更新的同学可以查看上篇文章深入浅出JVM(十六)之三色标记法与并发可达性分析
前言
前文描述过,当GC时需要枚举的GC根节点需要极短的停顿(STW)
而在遍历GC引用链时,如果用户线程是停顿的,那么不会改变引用,GC线程遍历标识即可
但随着堆内存中对象的增多,引用链会越来越长,如果持续让用户线程停顿,在某些需要低延迟的场景是不理想的
因此希望能在这个环节让用户线程和GC线程能够并发执行,并发执行就会存在改变对象引用,可能导致对象消失问题,其中可以使用增量更新和原始快照的方式解决,而CMS使用的就是增量更新
Concurrent Mark Sweep
CMS全称Concurrent Mark Sweep 并发标记清除收集器
CMS是老年代收集器,采用标记-清除算法,年轻代常用ParNew收集器,以最短停顿时间(低延迟)为目标的收集器
CMS并没有使用标记-整理算法,因为标记、清理阶段是和用户线程并发执行的,如果使用标记-整理算法可能会导致移动引用的位置导致出错
执行步骤
- 初始标记: 标记GC Roots直接关联的对象(STW时间极短)
- 并发标记: 从GC Roots直接关联对象开始遍历整个引用链的过程(耗时长,不需要停顿用户线程,用户线程与GC线程并发执行)
- 重新标记: 使用增量更新避免对象消失问题,修正并发标记期间改动的对象(需要STW,耗时比步骤1长,比步骤2短)
- 并发清除: 清理标记阶段判断已死亡的对象、重置状态等(该阶段也是并发执行)
执行图
参数设置
-XX:UseConcMarkSweepGC
- 老年代使用CMS垃圾收集器,新生代使用ParNew收集器
-XX:CMSInitiatingOccupancyFraction
- 设置老年代使用多少空间时开始垃圾回收
- 如果设置的太高,不够内存分配,不能满足并发执行,就会冻结用户线程启动Serial Old收集器,停顿时间就会变长
- 如果内存增长缓慢可以设置高一些,如果内存增长很快就要设置低一些 默认92%
-XX:+UseCMSCompactAtFullCollection
- 指定在FULL GC后是否对内存进行压缩整理
- 开启后,通过
-XX:CMSFullGCsBeforeCompaction
设置执行多少次FULL GC后进行内存压缩整理
-XX:ParallelCMSThreads
- 设置GC线程数量
特点
优点:
- 停顿时间短
只在初始标记,重新标记时STW - 并发执行
时间长的并发标记和并发清理与用户线程,加快响应速度,提升用户体验
缺点:
- 吞吐量降低
在处理器核数少时,GC线程与用户线程并发执行(使用i-CMS解决:减少GC线程独占时间,垃圾回收时间变长,对用户线程执行影响变小) - 无法处理浮动垃圾
增量更新通过记录新增引用来避免对象消失问题,可能出现浮动垃圾(不能在这一次的GC中被回收,只能下一次GC时被回收)
CMS不能等老年代满了再垃圾回收,因为与用户线程并发执行,所以需要留一部分内存 - 内存碎片多
多次垃圾回收后进行一次标记-整理算法,采用替补方案Serial Old
总结
本文根据并发垃圾收集器CMS深入浅出的解析CMS执行流程、优缺点以及配置参数等
CMS是一款主打低延迟、使用标记-清除算法的老年代并发垃圾收集器,年轻代常使用ParNew
CMS在初始标记时进行STW,接下来遍历引用链时与用户线程并发执行,然后让用户线程短暂STW使用增量更新进行重新标记,最后在并发进行清理、重置等工作
CMS的特点是在遍历引用链、清理时并发执行,能够使用户线程的停顿时间变短;但是带来吞吐量的降低,并且增量更新会导致浮动垃圾的出现,由于老年代使用标记-清除算法,不整理内存将会导致大对象无法存储,替补方案是使用Serial Old单线程标记-整理
如果老年代内存不足或需要整理内存时,会使用Serial Old 单线程处理,这可能导致延迟更高,在高版本中已经有G1等其他垃圾收集器代替CMS,CMS在JDK14时被移除
最后(一键三连求求拉~)
本篇文章将被收入JVM专栏,觉得不错感兴趣的同学可以收藏专栏哟~
本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~
有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜