大家好,我是树哥。前段时间有朋友去面试,被问到CMS垃圾收集器的细节,他没有回答。事实上,CMS垃圾收集器是收集器历史上一个非常重要的节点,它开启了GC收集器关注GC停顿时间的历史。今天就让舒哥带你一起学习吧!文章思维导图CMS收集器的历史如果你是资深Java开发者,你可能会对CMS垃圾收集器嗤之以鼻,然后说:CMS垃圾收集器早就落伍了,现在流行的G1和ZGC垃圾收集器没了!学这个东西没用!确实,正如一位资深开发人员所说,CMS垃圾收集器现在是一个比较过时的配置。CMS垃圾收集器在JDK1.5期间引入,在JDK9中被弃用,并在JDK14中被移除。用来代替CMS垃圾收集器的就是我们常说的G1垃圾收集器。但是G1垃圾收集器也是在CMS的基础上改进的,所以也有必要简单了解一下CMS垃圾收集器。CMS收集器介绍CMS(ConcurrentMarkSweep)垃圾收集器是第一个关注GC停顿时间的垃圾收集器。在此之前的垃圾收集器要么是串行垃圾收集,要么专注于系统吞吐量。这样的垃圾收集器对于交互性强的程序是非常不友好的,而CMS垃圾收集器的出现打破了这种尴尬局面。因此,CMS垃圾收集器从诞生之日起就受到大家的欢迎,至今仍有很多应用程序在使用它。CMS垃圾收集器之所以能够控制GC停顿时间,本质上是源于对“根可达算法”,即三色标记算法的改进。在CMS垃圾收集器出现之前,无论是Serious垃圾收集器,ParNew垃圾收集器,还是ParallelScavenge垃圾收集器,在进行垃圾收集时都需要StoptheWorld,即无法实现垃圾收集线程和用户线程并发执行。CMS垃圾回收器采用三色标记算法实现垃圾回收线程和用户线程并发执行,从而大大降低系统响应时间,提升强交互应用的体验。对于CMS垃圾回收器来说,其实是通过“mark-clear”算法来实现的。它的运行过程分为4个步骤,包括:初始标记、并发标记、重新标记和并发清除初始标记,指的是找到所有被GCRoots引用的对象,这个阶段需要“StoptheWorld”。这一步只是标记GCRoots可以直接关联的对象,不需要扫描整个引用,所以速度很快。并发标记是指扫描整个引用链寻找在“初始标记阶段”标记的对象,不需要“StoptheWorld”。扫描整个引用链需要花费大量时间,因此垃圾回收线程和用户线程并发执行可以减少垃圾回收时间,从而减少系统响应时间。这也是CMS垃圾收集器能够大大减少GC停顿时间的核心原因,但同时也带来了一些问题,即:在并发标记的时候,引用可能会发生变化,所以可能会出现标记泄漏(应该收集的垃圾不是回收)和多标准(不应该回收的垃圾被回收)。重标记是指纠正“并发标记”阶段出现的问题,需要“StoptheWorld”。并发标记阶段提到,由于垃圾回收算法和用户线程并发执行,虽然可以减少响应时间,但是会出现漏标和多标的问题。所以对于CMS回收器来说,需要这个阶段做一些验证,解决并发标记阶段出现的问题。并发清除是指清除标记为垃圾的对象,这个阶段不需要“StoptheWorld”。这个阶段垃圾回收线程和用户线程可以并发执行,所以不会影响用户的响应时间。引自《深入理解 Java 虚拟机》从上面的描述步骤我们可以看出,CMS之所以能够大幅减少GC停顿时间,本质上是将原来冗长的引用链扫描进行了分段。通过GC线程和用户线程并发执行,加上重标记和修正的方式,减少了垃圾回收的时间。CMS收集器的优缺点从上面的描述我们可以知道CMS收集器的优点是:并发垃圾收集,低暂停。但它也有以下明显的缺点:消耗大量的CPU资源。CMS回收器在并发标记和并发清理阶段需要开启多个线程进行处理,也就是说需要占用一部分线程资源,即CPU资源。默认情况下,CMS启用的垃圾回收线程数为(CPU数+3)/4。当CPU数量越大时,启用的垃圾收集线程数就越少。但是如果CPU数量较少,比如只有2个CPU时,垃圾回收线程就占了50%,也就是说垃圾回收需要50%的CPU时间。这会大大降低系统的吞吐量,这是不可接受的情况。无法处理漂浮垃圾。由于CMS并发标记阶段标记缺失,导致部分本应回收的垃圾对象无法回收。另外,CMS在进行并发清理的时候,同时有用户线程在运行,也会产生一些浮动垃圾。因此,对于CMS收集器来说,需要为这些浮动垃圾的存储预留一些空间。在JDK1.5的默认设置中,当老年代空间的已用空间大于68%时,CMS垃圾收集器就会开始垃圾清理。这个值比较保守,我们可以通过-XX:CMSInitiatingOccupancyFraction参数自行调整。在JDK1.6中,这个门槛被提高到92%。如果在CMS运行过程中发现预留的内存不能满足程序的需要,会提示“ConcurrentModeFailure”错误。这时候虚拟机采用了一个备份方案:暂时启用SerialOld收集器重新收集老年代的垃圾。此时,StoptheWorld可能需要很长时间。制造太空垃圾。由于CMS是基于“mark-clear”算法的回收器,会产生大量的空间碎片,在分配大对象时造成麻烦,会提前触发FullGC。为了解决这个问题,CMS回收器提供了-XX:+UseCMSCompactAtFullCollection参数来解决这个问题,意思是空间不够的时候整理空间。默认情况下启用此参数。这个参数通常和-XX:CMSFullGCsBeforeCompaction一起使用,用来设置不压缩的FullGC的次数,后面跟着一个带压缩的FullGC(默认值为0,表示每次进入FullGCtidy时分片).总结CMS收集器诞生于JDK1.5,迷失于JDK9,死于JDK14。它的诞生开启了垃圾收集器专注于优化GC停顿时间的历史,后续的G1和ZGC都是在CMS的基础上进行改进和优化。CMS收集器之所以能够实现对GC停顿时间的强控制,全归功于“根可达算法”的优化。它将串行引用链扫描拆分为“初始标记”和“并发标记”两个阶段,大大减少了GC停顿时间,最终通过“重新标记”解决并发执行问题。参考资料CMS低延迟垃圾收集器详解-掘金深入理解JAVA垃圾收集器CMS,G1工作流程原理-掘金深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)-周志明-微信阅读
