概述
CMS收集器是一款强调低延时的老年代垃圾收集器
CMS收集器的流程
初始标记
这个过程是STW的,主要分为两步:
1、标记GC Roots可达的老年代对象;
2、遍历GC Roots下的新生代对象能够可达的老年代对象;
并发标记
GC线程与工作线程并存,进行并发标记。标记那些GC Roots最终可达的对象。
标记过程中并发的维护card table与mod union table
并发标记相关数据结构
card table
背景:YGC时为了标记活动标记对象除了tracing GC Roots之外,还需要关心老年代引用的新生代对象情况。为了避免扫描老年代,引入了card table
实现:将老年代内存划分为一个个相等的卡页,每个卡页对应一个标记位用于标记是否存在老年代对象引用新生代对象的情况。是一种以空间换时间的思路(降低了写入屏障的开销)。
对于新生代:记录老年代到新生代的引用,youngGC时不用遍历整个老年代
对于老年代:记录并发标记开始引用发生变化的card,并发标记结束后需要处理这些card
新生代GC与老年代GC同时使用card table,所有会出现冲突的情况
mod union table
是一个bit位向量,一个bit表示一个card的状态
它由新生代垃圾收集器维护,新生代GC将card设置为clean前,把mod union table设置为dirty。
对于cms标记线程无论card table或者mod union table被标记为dirty,说明该card内引用状态发生变化,需要进行处理。
write Barrie
用户线程写对象引用的时候就触发write Barrie的逻辑,将对象(新引用的对象-增量标记)所处的card设置为dirty
并发预清理
通过参数CMSPreCleaningEnable选择关闭该阶段,默认启用。
处理dirty card(引用的增量更新)、新生代指向老年代的引用,降低remark阶段暂停时间。
可中断预清理
主要工作:
1、处理from和to区的对象,标记可达的老年代对象;
2、和上个阶段一样,扫描处理dirty card和mod union table中的对象。
被中断的三个条件:
1、可以设置中断的循环次数CMSMaxAbortablePrecleanLoops;
2、可以设置中断的最长等待时间CMSMaxAbortablePrecleanTime;
3、新生代Eden区使用率达到阈值CMSScheduleRemarkEdenPenetration
重新标记
1、遍历新生代对象,重新标记
2、根据GC Roots,重新标记
3、遍历老年代的Dirty Card和Mod Union Table,重新标记
如果新生代的使用率很高,需要遍历处理的对象也很多,会耗费较长时间。CMS提供了一个参数CMSScavengeBeforeRemark,指定在执行该阶段之前,会强制触发一次YGC。
并发清除
并发清除标记为不可达的对象,回收并合并空闲内存
并发重置
重新设置CMS相关的各种状态及数据结构,为下一次周期做准备
CMS收集器运行示意图
缺点
- 浮动垃圾
- 内存碎片:默认开启UseCMSCompactAtFullCollection参数,在FullFC时进行内存碎片的合并整理。CMSFullGCsBeforeCompaction参数控制多少次Full GC才进行整理。默认为0,即每次。
常见问题
concurrent mode failure:由于cms存在并发标记阶段,cms需要在回收时预留部分空间作为分配及新生代的担保、分配使用。当在老年代分配空间失败会触发concurrent mode failure并执行fullGC或串型的CMS。
Promotion failed:新生代GC时会根据历史晋升到老年代的大小,预估本次老年代是否能够容纳新生代对象晋升到老年代。如果预估足够,实际不够将会引发Promotion failed异常。
OutOfMemoryError:CMS垃圾收集器发现大部分时间都浪费在GC上就会抛出OutOfMemoryError异常,具体为98%的时间在GC但回收不到2%的空间
Full GC的触发场景:
- concurrent mode failure
- promotion failed
- perm gen分配失败
- System.gc()