初始标记和重新标记都必须stw,但范围极小、目的不同:前者仅标记gc roots直接可达对象以确立起点,后者修正并发标记期间的引用变动以确保准确性。

初始标记只扫“第一层”,重新标记专治“变动漏网”。两者都必须 Stop-The-World(STW),但目的不同、范围极小,是 CMS 在精度与停顿之间做的关键取舍。
初始标记:锁定起点,只标 GC Roots 直接连的对象
它不遍历对象图,只快速扫描 GC Roots(如线程栈、静态变量、JNI 引用等)直接引用的老年代对象,以及被年轻代存活对象直接引用的老年代对象。这部分数量极少,所以 STW 时间极短——通常不到几毫秒。相当于画出一个“种子集合”,为后续并发标记提供起点。
- 标的是“直接可达”,不是“全部可达”
- 不处理任何间接引用链,也不管对象是否跨代引用细节
- 必须 STW,否则 GC Roots 状态可能在扫描中变化,导致起点不准
重新标记:修正并发期的引用变更
在并发标记阶段,应用线程持续运行,对象引用关系可能频繁修改:比如某个对象刚被新引用指向,或原有引用被断开。这些变化不会被正在运行的并发标记线程实时感知,容易造成误判(把该活的标成死的,或反之)。重新标记就是专门捕获并修正这类“浮动垃圾”和“漏标对象”的最终校验环节。
- 重点扫描“脏卡”(Card Table 中被写入过的内存区域)和 CMS 的 Remembered Set
- 会重新检查在并发标记期间发生过引用更新的对象及其关联路径
- 虽需 STW,但工作量远小于完整标记,一般也控制在十几毫秒内
为什么不能只靠其中一次?
初始标记若试图覆盖全图,就会变成长时间 STW;重新标记若跳过,就会因并发干扰导致回收错误——比如把还在用的对象清掉,引发 NullPointerException。二者分工明确:前者保起点准,后者保过程稳。中间靠并发标记完成 98% 的主体工作,既不牺牲准确性,又极大压缩了用户感知的停顿。











