cms重新标记阶段的主要任务是找出在并发标记阶段(concurrent marking)期间,由于用户线程(mutator)的活跃导致被遗漏的对象引用关系。具体来说,它通过stop-the-world(stw)暂停所有应用线程,遍历整个堆内存,修正那些在并发标记过程中因对象引用变化而产生的“漏标”或“误标”情况,确保老年代中存活对象的集合是准确的,为后续的并发清除阶段提供可靠依据。

为什么需要重新标记而不是完全并发标记
因为CMS的并发标记阶段是与用户线程并发执行的,当GC线程正在扫描某个对象时,用户线程可能修改了该对象的引用字段,或者创建了新的引用指向已标记对象。这种并发修改会导致部分存活对象被错误地回收,或者部分死对象被保留。因此,必须引入一个短暂的STW阶段来修正这些不一致性。

重新标记的具体执行流程与影响
重新标记过程通常比初始标记和并发标记要快得多,因为它只需要处理增量更新。它会遍历根集(Root Set)以及所有在并发标记期间被修改过的对象引用,使用SATB(Snapshot At The Beginning)或CMY(Copy Modifying Young Generation)等算法来记录变化。一旦重新标记完成,GC线程会释放STW锁,进入并发清除阶段。这个阶段的停顿时间虽然短,但在大堆内存下仍可能对应用的响应延迟产生可感知的影响。
立即学习“Java免费学习笔记(深入)”;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
public class CMSReMarkingDemo {
public static void main(String[] args) throws Exception {
// 获取垃圾回收器信息
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean bean : gcBeans) {
System.out.println("GC Name: " + bean.getName());
System.out.println("Collection Count: " + bean.getCollectionCount());
}
// 触发一次Full GC以观察CMS行为
System.gc();
Thread.sleep(1000);
}
}
上述代码通过JMX接口查询垃圾回收器的统计信息。运行此程序并配合`-XX:+PrintGCDetails`参数,可以在控制台日志中看到类似`[GC (CMS Initial Mark) [1 CMS-initial-mark]`和`[GC (CMS Remark)]`的输出片段。其中`CMS Remark`即为重新标记阶段,其耗时通常远小于`CMS Concurrent Mark`阶段,验证了重新标记作为快速修正步骤的特性。
在实际应用中,如果频繁出现较长的重新标记停顿,可能需要考虑调整堆大小或切换到G1等更现代的垃圾回收器,以减少对应用吞吐量的干扰。


















