新标记,这里的Dirty Card大部分已经在clean阶段处理过
在第一步骤中,需要遍历新生代的全部对象,如果新生代的使用率很高,需要遍历处理的对象也很多,这对于这个阶段的总耗时来说,是个灾难(因为可能大量的对象是暂时存活的,而且这些对象也可能引用大量的老年代对象,造成很多应该回收的老年代对象而没有被回收,遍历递归的次数也增加不少),如果在AbortablePreclean阶段中能够恰好的发生一次YGC,这样就可以避免扫描无效的对象。
如果在AbortablePreclean阶段没来得及执行一次YGC,怎么办?
CMS算法中提供了一个参数:CMSScavengeBeforeRemark
,默认并没有开启,如果开启该参数,在执行该阶段之前,会强制触发一次YGC,可以减少新生代对象的遍历时间,回收的也更彻底一点。
不过,这种参数有利有弊,利是降低了Remark阶段的停顿时间,弊的是在新生代对象很少的情况下也多了一次YGC,最可怜的是在AbortablePreclean阶段已经发生了一次YGC,然后在该阶段又傻傻的触发一次。
所以利弊需要把握。
主动Old GC
这个主动Old GC的过程,触发条件比较苛刻:
- YGC过程发生Promotion Failed,进而对老年代进行回收
- 比如执行了
System.gc()
,前提是没有参数ExplicitGCInvokesConcurrent
- 其它情况…
如果触发了主动Old GC,这时周期性Old GC正在执行,那么会夺过周期性Old GC的执行权(同一个时刻只能有一种在Old GC在运行),并记录 concurrent mode failure 或者 concurrent mode interrupted。
主动GC开始时,需要判断本次GC是否要对老年代的空间进行Compact(因为长时间的周期性GC会造成大量的碎片空间),判断逻辑实现如下:
*should_compact =
UseCMSCompactAtFullCollection &&
((_full_gcs_since_conc_gc >= CMSFullGCsBeforeCompaction) ||
GCCause::is_user_requested_gc(gch->gc_cause()) ||
gch->incremental_collection_will_fail(true /* consult_young */));
在三种情况下会进行压缩:
- 其中参数
UseCMSCompactAtFullCollection
(默认true)和 CMSFullGCsBeforeCompaction
(默认0),所以默认每次的主动GC都会对老年代的内存空间进行压缩,就是把对象移动到内存的最左边。
- 当然了,比如执行了
System.gc()
,前提是没有参数ExplicitGCInvokesConcurrent
,也会进行压缩。
- 如果新生代的晋升担保会失败。
带压缩动作的算法,称为MSC,标记-清理-压缩,采用单线程,全暂停的方式进行垃圾收集,暂停时间很长很长…
那不带压缩动作的算法是什么样的呢?
不带压缩动作的执行逻辑叫Foreground Collect
,整个过程相对周期性Old GC来说,少了Precleaning和AbortablePreclean两个阶段,其它过程都差不多。
如果执行System.gc(),而且添加了参数ExplicitGCInvokesConcurrent
,这时并不属于主动GC,它会推进周期性Old GC的进行,比如刚刚执行过一次,并不会等2s后检查条件,而是立马启动周期性Old GC。