g1并发标记周期包含四个关键阶段:1.初始标记(stw),标记gc roots直接可达对象;2.根区域扫描(并发),扫描survivor区对老年代的引用;3.并发标记(并发),多线程遍历对象图并利用tams隐式标记新对象;4.最终标记(stw),通过satb机制处理引用变更导致的漏标问题。

G1收集器的并发标记周期不是一次性完成的扫描动作,而是一套分阶段、带保护机制的渐进式过程,核心目标是在应用持续运行的前提下,尽可能准确识别出所有存活对象,同时把STW时间压到最低。
并发标记周期包含哪几个关键阶段
它由四个子阶段组成,其中两个需要短暂停顿(STW),两个完全并发执行:
- 初始标记(Initial Marking):STW阶段,只标记从GC Roots直接可达的对象(比如线程栈、静态变量等),速度极快,通常几毫秒内完成;它也顺便为后续并发标记准备TAMS指针位置。
- 根区域扫描(Root Region Scan):并发执行,扫描Survivor区中可能持有老年代引用的对象,确保这些“跨代桥梁”不被漏掉;该阶段必须在下一次年轻代GC开始前完成,否则会触发退化处理。
- 并发标记(Concurrent Marking):真正耗时最长的阶段,GC线程与应用线程并行运行;通过多线程遍历对象图,将存活对象标记为灰色或黑色;所有新分配对象都落在TAMS之上,默认视为存活(隐式标记)。
- 最终标记(Final Marking):STW阶段,也是整个周期最关键的修正环节,专门处理并发期间因引用变更导致的漏标问题。
TAMS指针如何支撑并发标记
TAMS(Top at Mark Start)是G1实现“无锁分配+安全标记”的关键锚点:
- 并发标记启动瞬间,每个Region都会记录当前已分配的顶部地址,形成各自的TAMS值;
- TAMS下方区域:对象存在时间早于标记开始,需被GC线程主动扫描并显式标记;
- TAMS上方区域:全是标记开始后新分配的对象,G1默认全部视为存活,跳过标记步骤——这省去了写屏障对每条new指令的干预,大幅提升吞吐。
最终标记阶段到底在修什么
它主要解决两类并发导致的引用漂移问题:
- 应用线程在标记过程中修改了老对象的字段,断开了对某个白色对象的引用,而该对象本应被其他路径重新引用——若不处理,该对象会被错误回收;
- 应用线程新增了老对象对某个白色对象的引用,但该对象尚未被GC线程扫描到——若不处理,该对象会被漏标,变成浮动垃圾;
- G1依靠SATB(Snapshot-At-The-Beginning)机制捕获这些变更:写屏障会把被覆盖的旧引用目标对象加入SATB缓冲队列;最终标记阶段就遍历这些队列,把相关对象重新推入标记栈,确保传播完整。
为什么需要RSet和卡表配合
并发标记不能只看单个Region内部,还必须知道“谁引用了我”:
- 每个Region维护自己的Remembered Set(RSet),记录其他Region中指向本Region对象的引用;
- RSet底层依赖卡表(Card Table):堆被划分为512字节一张的卡页,一旦某Region内的对象被其他Region写入引用,对应卡页就被标记为“脏”;
- 并发标记时,GC线程会扫描所有脏卡页,快速定位跨Region引用,避免全堆扫描——这是G1能高效完成局部标记的根本保障。











