cms(concurrent mark sweep)垃圾回收器在并发标记阶段的主要任务是识别并标记堆内存中所有存活的对象,以便后续的清理阶段能够准确区分哪些空间可以回收。这一过程通过并发执行的方式,与用户业务线程交替运行,从而减少因全停顿(stop-the-world, stw)导致的延迟。具体做法是初始化一个根集合(root set),从 gc roots 出发进行可达性分析,并在标记过程中处理引用关系的变化。

CMS 如何确定初始的 GC Roots
CMS 在并发标记开始时,会经历一次短暂的 STW 阶段来建立初始的根集合。在这个阶段,JVM 会暂停所有应用线程,扫描栈帧、静态变量、JNI 引用等位置,找出所有直接引用的对象,并将它们加入到一个临时的根集合中。这个集合构成了后续并发标记的起点,确保不会遗漏任何存活对象的入口。

public class CMSRootExample {
public static Object staticRef = new Object();
public void method() {
Object localObj = new Object();
// 模拟 JVM 扫描栈和静态区
System.out.println("Roots identified: staticRef, localObj");
}
public static void main(String[] args) {
CMSRootExample instance = new CMSRootExample();
instance.method();
}
}
并发标记期间如何处理对象引用变化
由于并发标记与应用线程同时运行,应用线程可能会修改对象之间的引用关系(例如将 A 指向 B,或断开 A 对 B 的引用)。为了保证标记结果的准确性,CMS 采用了增量更新(Incremental Update)策略。当应用线程修改引用时,被修改的引用字段会被记录在一个“快照”中,或者在标记完成后重新检查这些变化的区域。这确保了即使对象图在动态变化,最终标记的存活对象集合也是正确的,避免了漏标或误标。

public class CMSReferenceChange {
Object target;
public void updateRef(Object newObj) {
// 模拟引用变更,触发增量更新机制
this.target = newObj;
}
public static void main(String[] args) {
CMSReferenceChange obj = new CMSReferenceChange();
obj.updateRef(new Object());
}
}
基于初始根集合大小的具体问题
如果初始根集合过大,会导致并发标记阶段的耗时显著增加,甚至可能因为标记速度跟不上对象创建速度而导致并发失败(Concurrent Mode Failure)。在实际程序中,可以通过调整 `-XX:CMSInitiatingOccupancyFraction` 参数来控制触发 CMS 收集的堆使用阈值,从而间接影响根集合的大小和标记效率。此外,减少长生命周期的大对象数量也能有效降低根集合的复杂度,提升标记阶段的性能。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











