深入解析 java 虚拟机:垃圾回收,最大并发标记清除垃圾回收器
CMS GC 线程会进入一个循环,每次它调用 sleepBeforeNextCycle()时会阻塞一段时间,唤醒后使用
CMSCollector::collect_in_background()清理老年代,如代码清单 10-17 所示:
代码清单 10-17 collect_in_background
void CMSCollector::collect_in_background(GCCause::Cause cause) {
...
switch (_collectorState) {
// 初始标记(STW)
case InitialMarking:{ReleaseForegroundGC x(this);
stats().record_cms_begin();
VM_CMS_Initial_Mark initial_mark_op(this);
VMThread::execute(&initial_mark_op);
}
break;
// 并发标记
case Marking: markFromRoots();break;
// 预清理
case Precleaning: preclean();break;
// 可中断预清理
case AbortablePreclean: abortable_preclean();break;
// 重新标记(STW)
case FinalMarking:{
ReleaseForegroundGC x(this);
VM_CMS_Final_Remark final_remark_op(this);
VMThread::execute(&final_remark_op);
}
break;
// 并发清理
case Sweeping: sweep(); // fallthrough
case Resizing: {
ReleaseForegroundGC x(this);
MutexLockerEx y(...);
CMSTokenSync z(true);
if (_collectorState == Resizing) {
compute_new_size();save_heap_summary();
_collectorState = Resetting;
}
break;
}
// 重置垃圾回收器的各种数据结构
case Resetting: ... break;
case Idling:
default: ShouldNotReachHere();break;
}
...
}
collect_in_background 实现了一个完整的 Old GC,代码使用状态机模式,通过_collectorState 状态转换来切换到不同的垃圾回收周期,简化了代码逻辑。
1. 初始标记
初始标记(InitiaMarking)是 Old GC 的第一个周期,它需要 Mutator 线程暂停,这一步通过安全点来保障,而虚拟机中能开启安全点的操作只能是 VMThread,所以 InitialMarking 阶段会创建一个 VM_CMS_Initial_Mark 的 VMOperation,当 VMThread 执行该 VMOperation 并协调所有线程进入安全点后,会调用
checkpointRootsInitialWork()进行初始标记,如代码清单 10-18 所示:
代码清单**10-18
chekcpointRootsInitialWork**
void CMSCollector::checkpointRootsInitialWork() {
// 确保位于安全点,并且处于 InitialMarking 阶段
assert(SafepointSynchronize::is_at_safepoint(), ...);
assert(_collectorState == InitialMarking, "just checking");
...
// 新生代指向老年代的引用
MarkRefsIntoClosure notOlder(_span, &_markBitMap);
...
if (CMSParallelInitialMarkEnabled) {
... // 使用多线程进行初始标记
CMSParInitialMarkTask tsk(this, &srs, n_workers);
if (workers->total_workers() > 1) {
workers->run_task(&tsk);
} else {
tsk.work(0);
}
} else {
... // 使用单线程进行初始标记
heap->cms_process_roots(...,?Older, &cld_closure);
}
...
}
代码清单 10-18 说明了并发和并行并不是互斥的概念,并发标记清除把整个标记清除细分为几个阶段,然后以 STW 的方式执行其中两个阶段,其他阶段允许 Mutator 线程和 GC 一起工作,在 STW 的两个阶段,垃圾回收器还可以充分发挥多核处理器的优势,使用多个线程进行回收工作,减少 STW 时间。
为了进一步减少 STW 时间,初始标记只会扫描并标记 GC Root 指向老年代的直接引用以及新生代指向老年代的直接引用,而所有间接引用都由后面的并发标记处理。
2. 并发标记
初始标记是从 GC Root 和新生代指向老年代记忆集出发,寻找直接可达的对象,接下来并发标记(Marking)是从这些对象出发,寻找间接可达的对象。
这一步由 markFromRoots()完成,该函数内部会创建 CMSConcMarkingTask 并发标记。CMSConcMarkingTask 包括标记逻辑和工作窃取逻辑,前者由 do_scan_and_mark 完成,后者由 do_work_stealing 完成。
标记的逻辑是每当发现初始标记的存活对象 cur,就将它放入_markStack,然后进入循环。每次从_markStack 中弹出一个对象,扫描 cur 的成员引用,直到_markStack 为空,这是一个典型的广度优先搜索过程,只是 CMS GC 在扫描 cur 成员引用时稍有改变,它不会将扫描到的 cur 的成员全部放入_markStack,而是选择性地放入,如图 10-10 所示。
图 10-10 BFS 过程中处理 cur 对象的成员引用
假设 cur 表示对象 C。对象 C 有成员对象 A 和 E,A 的地址位于 C 的前面,垃圾回收器会标记 A,并扫描 A 的成员引用 B;B 的地址位于 C 前面,标记 B 并扫描 B 的成员引用 D;D 的地址位于 C 后面,只标记 D,将 D 的成员放入_markStack 但是不继续扫描(本例中对象 D 没有成员);接着处理对象 E,E 地址位于 C 后面,所以只标记不扫描它的成员引用。
总结来说,扫描策略是找到存活对象 cur,如果它的成员对象地址位于 cur 前面,则标记并继续扫描成员对象,如果它的成员对象地址位于 cur 后面,则只标记不扫描成员对象。这样做实际上结合了广度优先搜索和深度优先搜索,好处是减小了_markStack 的大小,在该例中_markStack 最大仅包含一个元素,若直接使用广度优先搜索会导致_markStack 快速膨胀,虚拟机内存空间不足的情况。
3. 预清理
并发预清理和并发可中断预清理(Precleaning &&AbortablePreclean)是可选步骤,如果关闭
-XX:-CMSPrecleaningEnabled,虚拟机会跳过它直接执行下一阶段的重新标记。
如果上一阶段并发标记过程中 Mutator 线程修改了对象引用关系,比如创建了新生代指向老年代的引用,那么预清理可以发现这些修改,并标记老年代的对象图。可中断预清理与之类似,它会尝试若干次预清理过程,直到次数到达 GC 允许的上限,或者超过指定时间。两个阶段的意义在于做尽可能多的标记工作,减少下一阶段重新标记的 STW 时间。
4. 重新标记
重新标记(FinalMarking)过程会再次停止全部 Mutator 线程(STW),只允许垃圾回收线程。
因为初始标记到重新标记的间隔允许 Mutaor 线程和 GC 线程一起进行,所以可能产生大量从新生代指向老年代的引用,即新生代记忆集大增,也可能之前新生代已经存活的很多对象变成了死亡对象,但是 GC 不知道这个事实,仍然从 GC Root 和新生代记忆集出发标记存活对象,使本该死亡的对象被标记为存活对象,产生浮动垃圾。这是分代垃圾回收器面临的常见问题,如果开启-XX:+CMSScavengeBeforeRemark,在重新标记前 GC 会先对新生代进行垃圾回收,这样可以有效减少新生代记忆集大小,继而减少重新标记造成的 STW 时间。注意,以上讨论仅在两次 STW 标记期间新生代记忆集大增,或者大量新生代记忆集的对象从存活转变为死亡时才成立,如果随意开启该选项可能适得其反。
除了重新标记新增可选的新生代回收步骤外,重新标记过程与初始标记过程大致一样,两者都是向 VMThread 投递 VMOperation,区别在于前者的 VMOperation 调用
checkpointRootsInitialWork,后者调用 checkpointRootsFinalWork,如代码清单 10-19 所示:
代码清单 10-19?重新标记
vo
id CMSCollector::checkpointRootsFinalWork() {
...
// 根据虚拟机参数使用多线程重新标记或者使用单线程重新标记
if (CMSParallelRemarkEnabled) {
评论