这篇博客主要是介绍CMS垃圾收集器的执行过程,并不会进行很详细的说明,如果想了解每个过程的详细步骤,可以查看下面的参考博客。
CMS垃圾收集器首先需要注意的一点,是用来对堆中老年代当中的垃圾进行回收的,CMS收集器的主要目标就是致力于降低垃圾回收的暂停时间,CMS GC和Full GC是不同的,CMS GC主要关注的是老年代的GC, Full GC是针对整个堆的GC。
关于CMS垃圾收集器的过程
1、初始标记(InitialMarking)-SWT
2、并发标记(Marking)
3、预发清理(Precleaning)
4、可被中止的预清理(AbortablePreclean)
5、重标记(Remark)-SWT
6、并发清理
7、重置
- 初始标记
1、1根据GC Roots 标记出直接可达的老年代对象。
1、2遍历新生代对象,标记可达的老年代对象。
这个阶段需要暂停用户线程
- 并发标记
这个过程会继续前面初始标记的线程,从之前标记过得对象出发,将其关联的所有对象进行标记
- 并发预清理阶段
我们提到过整个CMS主要是为了在垃圾回收时减少用户线程暂停的时间,这个步骤就是为了加快重标记阶段的速度,而并发处理阶段。这个阶段主要是用来标记在并发标记时,从新生代晋升到老年代的对象,新分配到老年代的对象和并发阶段被修改了的老年代对象。
在老年代当中,采用的是Card Table的方式,将老年代分成大小512bits的区域,当老年代当中的引用发生变化时,就会将Card标记为Dirty.
这个过程会一直进行标记,除非一些条件满足了,才可以结束当前预清理阶段,进入到可中断的预清理阶段,接下来我们来看可中断的预清理触发条件。
- 可中断的预清理
在这个阶段会一直执行下面两个操作
1、在新生代当中一直遍历From Survivor和To Survivor区,标记可达的老年代对象
2、遍历在并发标记阶段标记为Dirty的Card.
在满足以下条件的时候就会停止上述动作
1、如果执行上述操作的时间超过了设定的CMSMaxAbortablePrecleanTime时间,默认是5s, 停止上述操作
2、如果Eden区的大小和使用率超过了设定值,也会进入可中断的预处理,CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration,这两个值的默认设定分别是2M,50%
如果在这个阶段发生了YGC,那么后面标记的时间就会被减少很多。
- 重新标记
这个过程会暂停工作进程,进行并行标记阶段,在之前的标记阶段都有可能发生,新生成的老年代对象,老年代对象的关系引用发生变化,新生代对象晋升到老年代对象等。因此这个阶段就是要再一次去标记之前遗漏掉的未被标记的对象。
1、遍历新生代对象,重新标记
2、遍历老年代的Dirty Card,重新进行标记
对于遍历新生代对象,如果在重新标记之前执行了一次YGC,就会使这个阶段遍历新生代时间降低,但是这个不是人为控制的,我们可以设置CMSScavengeBeforeRemark,默认关闭,这样在每次Remark之前都进行一次YGC。但是有个问题,很有可能Eden区,此时使用率并不高,还要被迫在执行一次YGC, 或者说在前面可中断的预处理中已经执行过一次YGC了,但是这里又要被迫执行一次YGC。
- 并发清理
清理掉未被标记的对象
- 重置
并发执行,重新设置CMS算法当中的内部数据,为下次清理做准备。
CMS收集器的缺点:
1、关于并发执行,CMS收集器当中,并发执行过程中的线程数量一般是(CPU个数+3)/4, 当CPU个数为2时,CMS收集器当中的线程占用到了50%, 这会降低用户使用效率
2、无法处理浮动垃圾,在并发清理阶段,用户线程正在运行,就会产生新的垃圾,并发清理时,用户线程需要分配新的内存空间,因此在CMS当中,不能等到老年代全部满了,才会执行GC, 可以设定,CMSInitiatingOccupancyFraction的值,来指定老年代使用率达到多少时,进行清理,默认是92%。如果这个值设置的过大,在并发清理阶段,用户线程需要的空间不能满足,就会导致Concurrent Mode Failure并发模式失败,这时候老年代会自动启动Serial Old方式进行老年代垃圾回收,就会造成更长时间的停顿。
3、碎片化问题,由于CMS收集器采用的是标记清除的方式来处理垃圾,当空间充足,但还不能进行大对象的分配,就要触发一次FullGC。当然CMS给出的解决方案是,UseCMSCompactAtFullCollection默认开启,在顶不住需要FullGC时,进行碎片化整理,这个过程是需要STW, 停顿时间就会变长。
CMS GC 和 Full GC有什么区别
CMS GC是一个后台线程定时去检查老年代当中的内存使用率是否超过了设定值,如果超过的话,进行一次GC。Full GC整个过程是使用VM Thread进行触发的,整个过程是Stop The World, 同时会判断是否需要压缩,将存活对象移动到一边,减少CMS GC 过程中产生的碎片。