CMS垃圾回收器

如果用Seria和Parallel系列的垃圾收集器:在垃圾回收的时,用户线程都会完全停止,直至垃圾回收结束!

img

CMS的全称:Concurrent Mark Sweep,翻译过来是并发标记清除

用CMS对比上面的垃圾收集器(Seria和Parallel和parNew):它最大的不同点就是并发:在GC线程工作的时候,用户线程不会完全停止,用户线程在部分场景下与GC线程一起并发执行。

但是,要理解的是,无论是什么垃圾收集器,Stop The World是一定无法避免的!

CMS只是在部分的GC场景下可以让GC线程与用户线程并发执行

CMS的设计目标是为了避免老年代 GC出现长时间的卡顿(Stop The World)

CMS的工作流程

CMS可以简单分为5个步骤:初始标记、并发标记、(并发预清理)、重新标记以及并发清除

从步骤就不难看出,CMS主要是实现了标记清除垃圾回收算法

初始标记的过程

初始标记会标记GCRoots直接关联的对象以及年轻代指向老年代的对象

初始标记这个过程是会发生Stop The World的。但这个阶段的速度算是很快的,因为没有向下追溯(只标记一层)

img

并发标记的过程

初始标记完了之后,就进入了并发标记阶段啦

并发标记这个过程是不会停止用户线程的(不会发生 Stop The World)。这一阶段主要是从GC Roots向下追溯,标记所有可达的对象。

并发标记在GC的角度而言,是比较耗费时间的(需要追溯)

img

并发标记这个阶段完成之后,就到了并发预处理阶段啦

并发预处理这个阶段主要想干的事情:希望能减少下一个阶段重新标记所消耗的时间

因为下一个阶段重新标记是需要Stop The World的

并发标记这个阶段由于用户线程是没有被挂起的,所以对象是有可能发生变化的

可能有些对象,从新生代晋升到了老年代。可能有些对象,直接分配到了老年代(大对象)。可能老年代或者新生代的对象引用发生了变化…

跨代引用的问题

针对老年代的对象,其实还是可以借助类card table的存储(将老年代对象发生变化所对应的卡页标记为dirty)

所以并发预处理这个阶段会扫描可能由于并发标记时导致老年代发生变化的对象,会再扫描一遍标记为dirty的卡页

对于新生代的对象,我们还是得遍历新生代来看看在并发标记过程中有没有对象引用了老年代..

不过JVM里给我们提供了很多参数,有可能在这个过程中会触发一次 minor GC(触发了minor GC 是意味着就可以更少地遍历新生代的对象)

img

重新标记的过程

并发预处理这个阶段阶段结束后,就到了重新标记阶段

重新标记阶段会Stop The World,这个过程的停顿时间其实很大程度上取决于上面并发预处理阶段(可以发现,这是一个追赶的过程:一边在标记存活对象,一边用户线程在执行产生垃圾)

img

并发清除的过程

最后就是并发清除阶段,不会Stop The World

一边用户线程在执行,一边GC线程在回收不可达的对象

这个过程,还是有可能用户线程在不断产生垃圾,但只能留到下一次GC 进行处理了,产生的这些垃圾被叫做“浮动垃圾”

完了以后会重置 CMS 算法相关的内部数据,为下一次 GC 循环做准备

img

CMS的缺点

  1. 空间需要预留:CMS垃圾收集器可以一边回收垃圾,一边处理用户线程,那需要在这个过程中保证有充足的内存空间供用户使用。如果CMS运行过程中预留的空间不够用了,会报错(Concurrent Mode Failure),这时会启动 Serial Old垃圾收集器进行老年代的垃圾回收,会导致停顿的时间很长。显然啦,空间预留多少,肯定是有参数配置的。
  2. 浮动垃圾:由于垃圾回收和用户线程是同时进行的,在进行标记或者清除的同时,用户的线程还会去改变对象的引用,使得原来某些对象不是垃圾,但是当 CMS 进行清理的时候变成了垃圾,CMS 收集器无法收集,只能等到下一次 GC。CMS 收集器无法处理浮动垃圾(Floating Garbage),可能出现 “Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生。如果在应用中老年代增长不是太快,可以适当调高参数 - XX:CMSInitiatingOccupancyFraction 的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能。
  3. 内存碎片问题:CMS本质上是实现了标记清除算法的收集器(从过程就可以看得出),这会意味着会产生内存碎片。由于碎片太多,又可能会导致内存空间不足所触发full GC,CMS一般会在触发full GC这个过程对碎片进行整理。整理涉及到移动/标记,那这个过程肯定会Stop The World的,如果内存足够大(意味着可能装载的对象足够多),那这个过程卡顿也是需要一定的时间的。

补充面试题:

1.CMS的过程?

初始标记、并发标记、(并发预清理)、重新标记以及并发清除

2,怎么标记垃圾的?

使用三色标记法

3.什么是三色标记法

在这里插入图片描述

 三色标记法,是把内存中的对象,标记为3种颜色,分布是:黑、灰、白。(上文图中的红色仅供参考)

  • 黑:表示该对象已经扫描到,并且它可触达的对象也已经扫描到;

  • 灰:表示该对象已经扫描到,但是它能触发的对象至少还有一个没有扫描到;

  • 白:表示该节点没有被扫描到;

4.CMS和G1的区别

  • G1和CMS都分为4个阶段,前三个阶段基本相同都为初始标记,并发标记,再次标记,区别在于最后清除阶段CMS是并发的,G1不是并发的,因此CMS最终会产生浮动垃圾,只能等待下次gc才能清除
  • G1可以管理整个堆,而CMS只能作用于老年代,并且CMS在老年代使用的是标记清除算法,会产生内存碎片,而G1使用标记整理算法,不会产生内存碎片
  • G1相比于CMS最大的区别是G1将内存划分为大小相等的Region,可以选择垃圾对象多的Region而不是整个堆从而减少STW,同时使用Region可以更精确控制收集,我们可以手动明确一个垃圾回收的最大时间

5.CMS什么时候会STW?为什么要STW(咋瓦鲁多)?

初始标记和重新标记的时候

因为初始标记标记的是GC Root,而GC Root容易变动,比如栈帧中的本地变量表。所以需要STW。

因为在重新标记之前是并发标记,在并发标记的期间会出现漏标和多标的对象,所以为了修正这部分对象,需要在重新标记期间STW。