CMS垃圾回收器详解

1.什么是CMS#

CMS主要设计目标:低停顿时间

CMS全称ConcurrentMarkSweep,作用于老年代,并发标记清除,并发是指垃圾回收和应用程序同时运行,这样的目的是为了降低STW的时间(200ms)

CMS垃圾回收器基于标记-清除算法实现,那么使用该算法的最大缺点也显而易见——大量的内存碎片。内存碎片过多时会给大对象分配带来麻烦,即会存在空间足够,但是连续的空间太小,这样的话就会触发Full GC,CMS发送FGC就会使用SerialOld进行老年代回收,这个过程如果内存比较大的话,就会产生很长的STW(几个小时-几天的STW)

CMS解决内存碎片的办法:使用 -XX:CMSFullGCsBefore-Compaction(JDK9之后废弃),即CMS在并发执行若干此Full GC之后,下一次Full GC会先进行碎片整理。(默认为0,即每次都整理)

2.CardTable#

由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card所指向的内存区域即可。在结构上,Card Table用BitMap来实现

参考链接:

https://segmentfault.com/a/1190000004682407

https://blog.csdn.net/z69183787/article/details/104939205

3.CMS的7个阶段#

CMS收集器的GC周期主要由7个阶段组成,其中有两个阶段会发生stop-the-world,其他阶段都是并发执行的。(亦有4个阶段、6个阶段等说法)

Phase 1: Initial Mark(初始化标记):

初始化标记阶段,是CMS GC的第一个阶段,也是标记阶段的开始。主要工作是标记可直达的存活对象。

主要标记过程:

​ 1.标记GCRoots能直接关联到的对象,由于这种对象比较少,所以标记速度快

​ 2.遍历被新生代存活对象所引用的老年代对象

程序执行情况:

​ 1.支持单线程或并行标记

​ 2.发生stop-the-world,暂停所有应用线程

image

(Marked obj:老年代绿色圆点表示被初始化标记的对象。)

Phase 2: Concurrent Mark(并发标记):

并发标记阶段,是CMS GC的第二个阶段。在该阶段,GC线程和应用线程将并发执行。也就是说,在第一个阶段(Initial Mark)被暂停的应用线程将恢复运行。

并发标记阶段的主要工作是,通过遍历第一个阶段(Initial Mark)标记出来的存活对象,继续递归遍历老年代,并标记可直接或间接到达的所有老年代存活对象。

image

(Current obj:该对象的引用关系发生变化,对下一个对象的引用被删除。)

由于在并发标记阶段,应用线程和GC线程是并发执行的,因此可能产生新的对象或对象关系发生变化,例如:

  • 新生代的对象晋升到老年代;
  • 直接在老年代分配对象;
  • 老年代对象的引用关系发生变更等等

对于这些对象,需要重新标记以防止被遗漏。为了提高重新标记的效率,本阶段会把这些发生变化的对象所在的Card标识为Dirty,这样后续就只需要扫描这些Dirty Card的对象,从而避免扫描整个老年代。

Phase 3: Concurrent Preclean(并发预清理):

在并发预清洗阶段,将会重新扫描前一个阶段标记的Dirty对象,并标记被Dirty对象直接或间接引用的对象,然后清除Card标识。

标记被Dirty对象直接或间接引用的对象:

image

清除Dirty对象的Card标识:

image

Phase 4: Concurrent Abortable Preclean(可中止的并发预清理):

本阶段尽可能承担更多的并发预处理工作,从而减轻在Final Remark阶段的stop-the-world。

在该阶段,主要循环的做两件事:

​ 1.处理 From 和 To 区的对象,标记可达的老年代对象;

​ 2.和上一个阶段一样,扫描处理Dirty Card中的对象。

具体执行多久,取决于许多因素,满足其中一个条件将会中止运行:

​ 1.执行循环次数达到了阈值;

​ 2.执行时间达到了阈值;

​ 3.新生代Eden区的内存使用率达到了阈值。

Phase 5: Final Remark(重新标记):

预清理阶段也是并发执行的,并不一定是所有存活对象都会被标记,因为在并发标记的过程中对象及其引用关系还在不断变化中。

因此,需要有一个stop-the-world的阶段来完成最后的标记工作,这就是重新标记阶段(CMS标记阶段的最后一个阶段)。主要目的是重新扫描之前并发处理阶段的所有残留更新对象。

主要工作:

  • 遍历新生代对象,重新标记;(新生代会被分块,多线程扫描)
  • 根据GC Roots,重新标记;
  • 遍历老年代的Dirty Card,重新标记。这里的Dirty Card,大部分已经在Preclean阶段被处理过了。

Phase 6: Concurrent Sweep(并发清理):

并发清理阶段,主要工作是清理所有未被标记的死亡对象,回收被占用的空间。

image

Phase 7: Concurrent Reset(并发重置):

并发重置阶段,将清理并恢复在CMS GC过程中的各种状态,重新初始化CMS相关数据结构,为下一个垃圾收集周期做好准备。

4.CMS采用的算法#

采用三色标记 + Incremental Update

https://www.cnblogs.com/ZT-666/p/15811798.html

5.CMS的优缺点#

  • 优点

    低停顿时间

    并发收集

  • 缺点

    1.内存碎片(Memory Fragmentation):内存碎片过多时会给大对象分配带来麻烦,即会存在空间足够,但是连续的空间太小,这样的话就会提前触发Full GC。CMS解决内存碎片的办法使用:-XX:CMSFullGCsBeforeCompaction参数(默认为0 指的是经过多少次FGC才进行压缩)

    2.浮动垃圾(Floating Garbage):可能出现“Concurrent Mode Failure”导致另一次Full GC的产生。在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或交叉运行,那么在并发标记阶段如何产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC的时释放这些之前未被回收的内存空间(浮动垃圾过多会导致分配对象时内存不足从而触发Full GC)。解决方法:降低触发CMS的阈值,–XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

    3.对CPU资源敏感:在并发阶段它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低

    CMS只要触发FGC,就会让SerialOld(单线程)进行老年代回收,这个过程如果内存比较大的话,就会产生很长的STW(几个小时-几天的STW)

posted @   ZT丶  阅读(1629)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
主题色彩