引子
带着问题去学习一个东西,才会有目标感,我先把一直以来自己对CMS的一些疑惑罗列了下,希望这篇学习笔记能解决掉这些疑惑,希望也能对你有所帮助。
- CMS出现的初衷、背景和目的?
- CMS的适用场景?
- CMS的trade-off是什么?优势、劣势和代价
- CMS会回收哪个区域的对象?
- CMS的GC Roots包括那些对象?
- CMS的过程?
- CMS和Full gc是不是一回事?
- CMS何时触发?
- CMS的日志如何分析?
- CMS的调优如何做?
- CMS扫描那些对象?
- CMS和CMS collector的区别?
- CMS的推荐参数设置?
- 为什么ParNew可以和CMS配合使用,而Parallel Scanvenge不可以?
一、基础知识
- CMS收集器:Mostly-Concurrent收集器,也称并发标记清除收集器(Concurrent Mark-Sweep GC,CMS收集器),它管理新生代的方式与Parallel收集器和Serial收集器相同,而在老年代则是尽可能得并发执行,每个垃圾收集器周期只有2次短停顿。
- 我之前对CMS的理解,以为它是针对老年代的收集器。今天查阅了《Java性能优化权威指南》和《Java性能权威指南》两本书,确认之前的理解是错误的。
- CMS的初衷和目的:为了消除Throught收集器和Serial收集器在Full GC周期中的长时间停顿。
- CMS的适用场景:如果你的应用需要更快的响应,不希望有长时间的停顿,同时你的CPU资源也比较丰富,就适合适用CMS收集器。
二、CMS的过程
CMS的正常过程
这里我们首先看下CMS并发收集周期正常完成的几个状态。
- (STW)初始标记:这个阶段是标记从GcRoots直接可达的老年代对象、新生代引用的老年代对象,就是下图中灰色的点。这个过程是单线程的(JDK7之前单线程,JDK8之后并行,可以通过参数CMSParallelInitialMarkEnabled调整)。
- 并发标记:由上一个阶段标记过的对象,开始tracing过程,标记所有可达的对象,这个阶段垃圾回收线程和应用线程同时运行,如上图中的灰色的点。在并发标记过程中,应用线程还在跑,因此会导致有些对象会从新生代晋升到老年代、有些老年代的对象引用会被改变、有些对象会直接分配到老年代,这些受到影响的老年代对象所在的card会被标记为dirty,用于重新标记阶段扫描。这个阶段过程中,老年代对象的card被标记为dirty的可能原因,就是下图中绿色的线:
- 预清理:预清理,也是用于标记老年代存活的对象,目的是为了让重新标记阶段的STW尽可能短。这个阶段的目标是在并发标记阶段被应用线程影响到的老年代对象,包括:(1)老年代中card为dirty的对象;(2)幸存区(from和to)中引用的老年代对象。因此,这个阶段也需要扫描新生代+老年代。【PS:会不会扫描Eden区的对象,我看源代码猜测是没有,还需要继续求证】
-
可中断的预清理:这个阶段的目标跟“预清理”阶段相同,也是为了减轻重新标记阶段的工作量。可中断预清理的价值:在进入重新标记阶段之前尽量等到一个Minor GC,尽量缩短重新标记阶段的停顿时间。另外可中断预清理会在Eden达到50%的时候开始,这时候离下一次minor gc还有半程的时间,这个还有另一个意义,即避免短时间内连着的两个停顿,如下图资料所示:
在预清理步骤后,如果满足下面两个条件,就不会开启可中断的预清理,直接进入重新标记阶段:- Eden的使用空间大于“CMSScheduleRemarkEdenSizeThreshold”,这个参数的默认值是2M;
- Eden的使用率大于等于“CMSScheduleRemarkEdenPenetration”,这个参数的默认值是50%。
如果不满足上面两个条件,则进入可中断的预清理,可中断预清理可能会执行多次,那么退出这个阶段的出口有两个(源码参见下图):
- 设置了CMSMaxAbortablePrecleanLoops&#x