CMS垃圾回收器介绍

1. CMS垃圾回收器

CMS是老年代回收器,只能回收老年代的对象,在收集过程中可以与用户线程并发操作。CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。CMS收集器可以通过参数:-XX:+UseConcMarkSweepGC启用。

2. 收集过程

CMS收集器是基于算法标记-清除来实现的,整个过程分为5步:

  1. 初始标记
    记录能被GC Root直接引用的对象,触发一次STW,但是这次STW很快,因为在标记的过程中不会标记一整条引用链的对象,如图所示,只记录红色箭头关联到的对象,不记录黑色箭头。
    在这里插入图片描述

  2. 并发标记
    从GC Roots的直接引用对象开始依次扫描(对上面的黑色箭头的链路做扫描),这个过程需要比较多的时间,用户线程和GC线程同时执行,不会产生STW,因为在扫描的过程中用户线程还在不断的执行所以可能会出现标记过的对象又变成了垃圾。

  3. 重新标记
    重新标记需要Stop The World,这个阶段是为了修正在并发标记阶段产生的浮动垃圾,对标记过的对象进行。

  4. 并发清除
    GC线程和用户线程同时进行,开始正式清除未被标记的垃圾,在此阶段也会产生垃圾(浮动垃圾),产生垃圾后无法清除,只能留待下一次GC。

在清除完后会重置标记信息。

CMS收集过程如下图所示:
image

收集过程总结:

  1. 在初始标记和重新标记有两次stop the world的标记操作
  2. 初始标记只标记GC Root关联的对象,速度很快
  3. 在初始标记触发STW的时候,它的标记方式还是原始的更改对象头MarkWord的GC标记字段,但是在并发标记阶段,因为是用户线程和GC线程同时在跑,所以采用的是三色标记的方式进行垃圾标记。
4. CMS三色标记

三色标记将对象的标记过程分为三种颜色:白色、灰色、黑色

  • 白色:对象的默认颜色,从GC Root开始扫描,如果对象是不可达对象的话就是白色,也就是垃圾对象,在并发清理的时候会清理掉。
  • 灰色:当前对象已经被扫描过,但是当前对象所依赖的其他对象还没有被扫描。
  • 黑色:当前对象和它所依赖的对象都已经被扫描过,不会对黑色对象和引用的对象再次进行扫描。

三色标记使用在并发标记阶段,使用三色标记会导致两个问题,一个是漏标,一个是多标。

  1. 漏标
    比如现在有ABCD四个对象,A依赖了B和C,B依赖了D,初始标记之后A对象已经被扫描过了所以是灰色,其他对象是白色:
    在这里插入图片描述

继续往下执行扫描B和C,当B和C扫描完之后,A变成了黑色,B变成了灰色,C是黑色,D还是白色:
在这里插入图片描述

此时如果用户线程把B和D的引用去掉,让C依赖D,建立起C和D的关系之后B变成了黑色:
在这里插入图片描述

那么问题来了,C已经是黑色就不会再对其依赖的对象扫描了,但事实上C还有一个依赖对象D没有被扫描。此时如果进行垃圾回收的话D就会被回收掉,这就是所谓的漏标问题。

  1. 多标
    还是用上面的例子进行说明,比如现在AB是黑色,C是灰色,D是白色,当GC正在扫描D的时候,B被置空了,从逻辑上讲B是垃圾,理应被回收,但是因为GC不会对黑色对象做重复扫描所以B还是黑色,在进行垃圾清理的时候不会被回收,只能等下次GC的时候再进行重新标记扫描。这种情况相对于漏标来说还行,起码不会导致系统出BUG。

漏标的解决方案
CMS使用增量更新的方式解决三色标记漏标问题。
增量更新:
将新增的引用维护到一个集合里面,将引用的源头变成灰色,等待重新标记阶段再重新进行一次扫描。比如:当D的引用指向了C,则会将C变成灰色,并将C放在一个新增引用的集合里面;在重新标记阶段会将C作为根节点继续向下扫描。

5. 为什么CMS要用标记清除法收集垃圾?

CMS的垃圾回收阶段是并发回收的,如果使用标记整理法收集的话,对象的内存地址会进行移动,因为用户线程还在执行,为了避免因内存地址移动带来的bug,还需要对用户线程的对象指针进行维护,在这个过程中肯定会STW,这样做就提高了垃圾清理的时长,停顿时间也变长了,不符合CMS一获取最短回收停顿时间为目的设计的初衷。

6. Concurrent Mode Failure(并发模式失败)

CMS的垃圾回收周期很长,但是他的STW时间是分开两部分的,比如总的STW要100ms,可能他会在初始标记消耗20ms,重新标记消耗80ms,对于用户来说能感知到的停顿时长可能只有80ms。因为CMS的回收周期很长,所以在垃圾很多的情况下可能出现上次GC周期还没执行完就又触发了GC,称之为Concurrent Mode Failure。出现这种情况之后,Java虚拟机就会启动预备方案,启用Serial Old收集器替换CMS收集器,这时候整个GC过程都会Stop The World。
因为是采用标记清除算法进行垃圾回收,所以会存在内存碎片的问题,通过参数-XX:+UseCMSCompactAtFullCollection可以设置清除之后再做一次整理。

7.CMS优缺点
  • 优点:并发收集、低停顿。
    其实最主要的是CMS把收集过程中步骤拆分了,而最耗时的操作都是并发执行,自然就会低停顿了。
  • 缺点:产生大量空间碎片(可以通过配置重新整理,但是加长停顿时间)、并发阶段会降低吞吐量。无法处理浮动垃圾。
    CMS采用的是标记-清除算法,所以会产生大量的空间碎片。
8. 参数设置
#老年代采用CMS收集器收集
-XX:+UseConcMarkSweepGC
当设置使用CMS垃圾回收器进行垃圾收集后,在老年代使用CMS进行垃圾回收,在年轻代默认开启ParNew回收。
所以说CMS是一款老年代回收器。


#用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
-XX:+UseCMSCompactAtFullCollection

#设置在执行多少次Full GC后对内存空间进行压缩整理。默认是0
-XX:CMSFullGCsBeforeCompaction=

#设定CMS的线程数量,不指定默认是CPU核心数
-XX:+ParallelCMSThreads=

#默认情况下初始标记是单线程的,这个参数可以让他多线程执行,可以减少STW
XX:+CMSParallellInitialMarkEnabled

#使用多线程进行重新标记,目的也是为了减少STW
-XX:+CMSParallelRemarkEnabled


9. JDK版本后续变化

JDK9新特性: CMS被标记为Deprecate(过期)了
如果对JDK 9及以上版本的HotSpot虚拟机使用参数一XX:+UseConcMarkSweepGC来开启CMS收集器的话,用户会收到一个警告信息,提示CMS未来将会被废弃。

JDK14新特性:删除CMS垃圾回收器
移除了CMS垃圾收集器,如果在JDK14中使用一XX: +UseConcMarkSweepGC的话,JVM不会报错,只是给出一个warning信息,但是不会exit。JVM会自动回退以默认GC方式启动JVM

10. 配合其他收集器使用

在老年代设置开启CMS收集器后,在新生代会自动默认开启ParNew收集器。另外在JDK8版本把Serial+CMS这个新生代和老年代的收集器组合标记为废弃。