CMS垃圾回收器

王猴卖瓜

共 2357字,需浏览 5分钟

 · 2021-03-11



2 CMS简介

CMS(Concurrent Mark and Swee 并发-标记-清除),是一款基于并发、使用标记清除算法的垃圾回收算法,只针对老年代进行垃圾回收。CMS收集器工作时,尽可能让GC线程和用户线程并发执行,以达到降低STW时间的目的

通过以下命令行参数,启用CMS垃圾收集器:

-XX:+UseConcMarkSweepGC

值得补充的是,下面介绍到的CMS GC是指老年代的GC,而Full GC指的是整个堆的GC事件,包括新生代、老年代、元空间等,两者有所区分

3 新生代垃圾回收

能与CMS搭配使用的新生代垃圾收集器有Serial收集器和ParNew收集器。这2个收集器都采用标记复制算法,都会触发STW事件,停止所有的应用线程。不同之处在于,Serial是单线程执行,ParNew是多线程执行

4 老年代垃圾回收

CMS GC以获取最小停顿时间为目的,尽可能减少STW时间,可以分为7个阶段

  • 阶段 1: 初始标记(Initial Mark)

此阶段的目标是标记老年代中所有存活的对象, 包括 GC Root 的直接引用, 以及由新生代中存活对象所引用的对象,触发第一次STW事件

这个过程是支持多线程的(JDK7之前单线程,JDK8之后并行,可通过参数CMSParallelInitialMarkEnabled调整)

  • 阶段 2: 并发标记(Concurrent Mark)

此阶段GC线程和应用线程并发执行,遍历阶段1初始标记出来的存活对象,然后继续递归标记这些对象可达的对象

  • 阶段 3: 并发预清理(Concurrent Preclean)

此阶段GC线程和应用线程也是并发执行,因为阶段2是与应用线程并发执行,可能有些引用关系已经发生改变。
通过卡片标记(Card Marking),提前把老年代空间逻辑划分为相等大小的区域(Card),如果引用关系发生改变,JVM会将发生改变的区域标记位“脏区”(Dirty Card),然后在本阶段,这些脏区会被找出来,刷新引用关系,清除“脏区”标记

  • 阶段 4: 并发可取消的预清理(Concurrent Abortable Preclean)

此阶段也不停止应用线程. 本阶段尝试在 STW 的 最终标记阶段(Final Remark)之前尽可能地多做一些工作,以减少应用暂停时间
在该阶段不断循环处理:标记老年代的可达对象、扫描处理Dirty Card区域中的对象,循环的终止条件有:
1 达到循环次数
2 达到循环执行时间阈值
3 新生代内存使用率达到阈值

  • 阶段 5: 最终标记(Final Remark)

这是GC事件中第二次(也是最后一次)STW阶段,目标是完成老年代中所有存活对象的标记。在此阶段执行:
1 遍历新生代对象,重新标记
2 根据GC Roots,重新标记
3 遍历老年代的Dirty Card,重新标记

  • 阶段 6: 并发清除(Concurrent Sweep)

此阶段与应用程序并发执行,不需要STW停顿,根据标记结果清除垃圾对象

  • 阶段 7: 并发重置(Concurrent Reset)

此阶段与应用程序并发执行,重置CMS算法相关的内部数据, 为下一次GC循环做准备

5 CMS常见问题

最终标记阶段停顿时间过长问题

CMS的GC停顿时间约80%都在最终标记阶段(Final Remark),若该阶段停顿时间过长,常见原因是新生代对老年代的无效引用,在上一阶段的并发可取消预清理阶段中,执行阈值时间内未完成循环,来不及触发Young GC,清理这些无效引用

通过添加参数:-XX:+CMSScavengeBeforeRemark。在执行最终操作之前先触发Young GC,从而减少新生代对老年代的无效引用,降低最终标记阶段的停顿,但如果在上个阶段(并发可取消的预清理)已触发Young GC,也会重复触发Young GC

并发模式失败(concurrent mode failure) & 晋升失败(promotion failed)问题

并发模式失败:当CMS在执行回收时,新生代发生垃圾回收,同时老年代又没有足够的空间容纳晋升的对象时,CMS 垃圾回收就会退化成单线程的Full GC。所有的应用线程都会被暂停,老年代中所有的无效对象都被回收

晋升失败:当新生代发生垃圾回收,老年代有足够的空间可以容纳晋升的对象,但是由于空闲空间的碎片化,导致晋升失败,此时会触发单线程且带压缩动作的Full GC

并发模式失败和晋升失败都会导致长时间的停顿,常见解决思路如下:

  • 降低触发CMS GC的阈值,即参数-XX:CMSInitiatingOccupancyFraction的值,让CMS GC尽早执行,以保证有足够的空间

  • 增加CMS线程数,即参数-XX:ConcGCThreads,

  • 增大老年代空间

  • 让对象尽量在新生代回收,避免进入老年代

内存碎片问题

通常CMS的GC过程基于标记清除算法,不带压缩动作,导致越来越多的内存碎片需要压缩,常见以下场景会触发内存碎片压缩:

  • 新生代Young GC出现新生代晋升担保失败(promotion failed)

  • 程序主动执行System.gc()

可通过参数CMSFullGCsBeforeCompaction的值,设置多少次Full GC触发一次压缩,默认值为0,代表每次进入Full GC都会触发压缩,带压缩动作的算法为上面提到的单线程Serial Old算法,暂停时间(STW)时间非常长,需要尽可能减少压缩时间

浏览 51
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报