标记阶段优化核心是“标得准、标得快、标得稳”:采用位图标记提升缓存命中与并行性;结合三色标记与satb/增量更新写屏障防漏标;通过根分区、对象年龄过滤和记忆集减少冗余扫描,显著缩短stw时间。

标记阶段是JVM垃圾回收的起点,直接影响后续清理效率和停顿时间。优化关键不在“标得更多”,而在“标得准、标得快、标得稳”——尤其要减少STW(Stop-The-World)时长、避免漏标误标、适配不同代际特征。
用位图标记替代对象头标记
传统方式在对象头设标记位,会修改对象内存布局,影响CPU缓存局部性。现代JVM(如HotSpot)普遍采用位图(Bitmap)方案:为堆内存划分固定粒度区域(如128字节/位),用独立位数组记录存活状态。
- 不污染对象内存,提升TLB与CPU缓存命中率
- 支持多线程并行标记——每个线程处理位图的不同段
- 位操作(set/clear/test)比读写对象头更轻量,尤其适合大堆场景
三色标记 + 增量更新(SATB)或原始快照(Snapshot-At-The-Beginning)
并发标记中,应用线程可能修改引用关系,导致漏标。主流解决思路是引入三色抽象(白=未访问、灰=已标记但子节点未扫描、黑=已完全扫描),再配合屏障机制保障一致性:
- SATB(适用于G1):在引用被覆盖前,将原引用指向的对象“快照”加入灰色集合,确保不会漏标
- 增量更新(适用于CMS):在引用字段被写入新值时,将新对象标记为灰色,防止其下游对象被跳过
- 两者都依赖写屏障(Write Barrier)插入少量指令,开销可控但必须精准部署
分代+根分区(Root Partitioning)加速初始标记
初始标记(Initial Mark)虽短,却是STW阶段。优化重点是缩小根集扫描范围:
- 只扫描GC Roots中“活跃部分”:比如仅遍历当前线程栈帧的局部变量,而非全部栈内存
- 对方法区静态变量做增量扫描——只检查自上次GC后被修改过的类或字段
- G1中将Java栈按线程分片,各GC线程并行处理不同栈片段,显著缩短STW时间
避免冗余标记:提前终止与对象年龄过滤
并非所有对象都需要参与每轮标记。可结合对象生命周期特征减少工作量:
- 新生代GC(如Minor GC)通常不扫描老年代对象——除非存在跨代引用,此时只需检查记忆集(Remember Set)记录的脏卡
- 对已晋升多次的老对象(如年龄≥15),若所在Region长期无变化,可启用“豁免标记”策略(需配合对象年龄统计与Region热度分析)
- 某些收集器(ZGC/Shenandoah)在标记时跳过只读类元数据区,因其本身不可变











