CMS垃圾收集器

2018/12/03 Java JVM

CMS的特点

  1. 老年代
  2. 标记-清除算法(之前的都是标记-整理算法),产生内存碎片。
  3. 并发收集,低停顿时间,适用于用户交互多的场景。 CMS是HotSpot在JDK1.5推出额第一款真正意义上的并发(Concurrent)收集器==,第一次实现了让垃圾回收线程与用户线程同时工作==。

CMS的缺点

  1. 对CPU资源非常敏感。
    其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。
  2. 无法处理浮动垃圾
  3. 基于“标记-清除”算法,产生内存碎片。但是提供-XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction参数控制执行内存碎片压缩。

参数

-XX:UseConcMarkSweepGC 显示使用CMS收集器

CMS的收集过程

image

初始标记(CMS initial mark)

是CMS中两次Stop The World中的一次,标记存活的对象。

  1. 标记GC Roots可达的老年代对象。
  2. 遍历新生代对象,标记可达的老年代对象。

-XX:+CMSParallelInitialMarkEnabled

image

并发标记(CMS concurrent mark)

image

  1. 从初始标记阶段的对象开始找出所有存活的对象。
  2. 因为是和用户程序并发运行,在运行期间会发生新生代对象晋升到老年代、直接在老年代分配对象、更新老年代对象的引用关系等。需要重新标记这些对象,否则会出现漏标。为了提高重新标记的效率,避免之后要重新扫描整个老年代,在并发标记阶段,上述对象所在的Card会被标识为Dirty,后面的步骤中只需要重新扫描被标记为Dirty Card中的对象。
  3. 可能导致==Concurrent Mode Failure==。

Concurrent Mode Failure 在并发标记阶段,用户程序并行执行,不断有新对象被分配至老年代,如果此时老年代空间不足,就会造成Concurrent Mode Failure, ==CMS将退化成SerialOld==,单线程、Stop The World的对老年代对象进行一次回收。

Card Table 卡表
Card Table实质是一个比特位的集合,每一个比特位用来表示老年代的某一个区域。
image
好处:不需要重新扫描整个老年代、节省时间,也不需要记录所有需要重新标记的对象,节省空间。

并发预清理阶段(Concurrent Preclean[non-abortable])

image

该阶段扫描所有标记为Dirty的card(新分配至老年代或老年代引用改变),处理前一个阶段因为引用关系改变导致没有标记到的存活对象。

如图中所示,在并发清理阶段,节点3的引用被改变,指向了节点6,则节点3的card标记为Dirty,节点6在预清理阶段被标记为存活。

image

并发可终止预处理(Concurrent Abortable Preclean)

重新标记(Final Remark)会扫描整个新生代,并且需要Stop The World,因此,为了减少重新标记(Final Remark)的时间,在并发预清理阶段(Concurrent Preclean)结束后,如果新生代(Eden)大小超过CMSScheduleRemarkEdenSizeThreashold的值,进入并发可终止预清理阶段(Concurrent Abortable Preclean),并发地重复标记新生代和老年代,直到新生代(Eden)的大小超过CMSScheduleRemarkEdenPenetration,或者该阶段执行时间超过CMSMaxAbortablePrecleanTime,进入重新标记(Final Remark)阶段。

并发可终止预处理(Concurrent Abortable Preclean)阶段期望在执行过程中新生代(Eden)能够发生一次YoungGC,以大量减少重新标记(Final Remark)阶段需要扫描的工作量。

重新标记(Final Remark)

该阶段需要Stop The World,任务是完成标记老年代的所有存活对象。
该阶段也会扫描新生代,因为被新生代对象引用的老年代对象也需要标记为存活。
因此,所有上面步骤中标记的老年代,和所有引用了老年代的新生代对象,都会成为这一阶段的”GC Root”
-XX:+CMSScavengeBeforeRemark,指定在重新标记阶段前执行一次young GC,这样只需要扫描Survivor区,大大减少了扫描时间。

并发清理(Concurrent Sweep)

并发回收没有被标记存活的老年代对象。
由于用户线程仍然在运行,==新的垃圾没有被标记==,只能在下一次GC时清理,这部分垃圾被称为浮动垃圾

并发重置(Concurrent Reset)

重置CMS收集器的数据结构,等待下一次回收。

参数设置

基本参数 作用
-XX:+UseConcMarkSweepGC 开启CMS
-XX:CMSInitiatingOccupancyFraction=85 触发FullGC的老年代使用百分比
-XX:+UseCMSCompactAtFullCollection 在CMS执行FullGC后执行内存压缩,防止产生过多内存碎片
-XX:CMSFullGCsBeforeCompaction=5 执行多少次FullGC后执行一次内存压缩
-XX:ParallelGCThreads=8 并发标记和清除时的线程数
-XX:CMSScheduleRemarkEdenSizeThreshold=2M 启动并发可终止预处理(Concurrent Abortable Preclean)时的新生代大小阈值
-XX:CMSScheduleRemarkEdenPenetration=50 结束并发可终止预处理(Concurrent Abortable Preclean)时的新生代已用大小百分比阈值
-XX:CMSMaxAbortablePrecleanTime=5000 并发可终止预处理(Concurrent Abortable Preclean)最长运行时间(ms)
-XX:ScavengeBeforeRemark 强制在并发清理(Concurrent Sweep)前执行一次YoungGC

Full GC

导致CMS Full GC的可能原因有:

  1. Promotion Failure,年轻代晋升时老年代没有足够的连续空间容纳,很有可能是内存碎片导致。
  2. Concurrent Mode Failure,并发过程中JVM计算估计在并发过程结束前堆空间就会满。

refs:

  • https://blog.csdn.net/zqz_zqz/article/details/70568819
  • https://plumbr.eu/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep
  • https://blogs.oracle.com/poonam/understanding-cms-gc-logs
  • http://www.importnew.com/27822.html
本文地址:https://cheng-dp.github.io/2018/12/03/gc-cms/

Search

    Table of Contents