CMS是老年代回收器,只能回收老年代的对象,在收集过程中可以与用户线程并发操作。CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。CMS收集器可以通过参数:-XX:+UseConcMarkSweepGC启用。
CMS收集器是基于算法标记-清除来实现的,整个过程分为5步:
初始标记
记录能被GC Root直接引用的对象,触发一次STW,但是这次STW很快,因为在标记的过程中不会标记一整条引用链的对象,如图所示,只记录红色箭头关联到的对象,不记录黑色箭头。
在这里插入图片描述
并发标记
从GC Roots的直接引用对象开始依次扫描(对上面的黑色箭头的链路做扫描),这个过程需要比较多的时间,用户线程和GC线程同时执行,不会产生STW,因为在扫描的过程中用户线程还在不断的执行所以可能会出现标记过的对象又变成了垃圾。
重新标记
重新标记需要Stop The World,这个阶段是为了修正在并发标记阶段产生的浮动垃圾,对标记过的对象进行。
并发清除
GC线程和用户线程同时进行,开始正式清除未被标记的垃圾,在此阶段也会产生垃圾(浮动垃圾),产生垃圾后无法清除,只能留待下一次GC。
在清除完后会重置标记信息。
CMS收集过程如下图所示:
收集过程总结:
三色标记将对象的标记过程分为三种颜色:白色、灰色、黑色
三色标记使用在并发标记阶段,使用三色标记会导致两个问题,一个是漏标,一个是多标。
继续往下执行扫描B和C,当B和C扫描完之后,A变成了黑色,B变成了灰色,C是黑色,D还是白色:
此时如果用户线程把B和D的引用去掉,让C依赖D,建立起C和D的关系之后B变成了黑色:
那么问题来了,C已经是黑色就不会再对其依赖的对象扫描了,但事实上C还有一个依赖对象D没有被扫描。此时如果进行垃圾回收的话D就会被回收掉,这就是所谓的漏标问题。
漏标的解决方案
CMS使用增量更新的方式解决三色标记漏标问题。
增量更新:
将新增的引用维护到一个集合里面,将引用的源头变成灰色,等待重新标记阶段再重新进行一次扫描。比如:当D的引用指向了C,则会将C变成灰色,并将C放在一个新增引用的集合里面;在重新标记阶段会将C作为根节点继续向下扫描。
CMS的垃圾回收阶段是并发回收的,如果使用标记整理法收集的话,对象的内存地址会进行移动,因为用户线程还在执行,为了避免因内存地址移动带来的bug,还需要对用户线程的对象指针进行维护,在这个过程中肯定会STW,这样做就提高了垃圾清理的时长,停顿时间也变长了,不符合CMS一获取最短回收停顿时间为目的设计的初衷。
CMS的垃圾回收周期很长,但是他的STW时间是分开两部分的,比如总的STW要100ms,可能他会在初始标记消耗20ms,重新标记消耗80ms,对于用户来说能感知到的停顿时长可能只有80ms。因为CMS的回收周期很长,所以在垃圾很多的情况下可能出现上次GC周期还没执行完就又触发了GC,称之为Concurrent Mode Failure。出现这种情况之后,Java虚拟机就会启动预备方案,启用Serial Old收集器替换CMS收集器,这时候整个GC过程都会Stop The World。
因为是采用标记清除算法进行垃圾回收,所以会存在内存碎片的问题,通过参数-XX:+UseCMSCompactAtFullCollection可以设置清除之后再做一次整理。
#老年代采用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
JDK9新特性: CMS被标记为Deprecate(过期)了
如果对JDK 9及以上版本的HotSpot虚拟机使用参数一XX:+UseConcMarkSweepGC来开启CMS收集器的话,用户会收到一个警告信息,提示CMS未来将会被废弃。
JDK14新特性:删除CMS垃圾回收器
移除了CMS垃圾收集器,如果在JDK14中使用一XX: +UseConcMarkSweepGC的话,JVM不会报错,只是给出一个warning信息,但是不会exit。JVM会自动回退以默认GC方式启动JVM
在老年代设置开启CMS收集器后,在新生代会自动默认开启ParNew收集器。另外在JDK8版本把Serial+CMS这个新生代和老年代的收集器组合标记为废弃。