JVM底层源码深度解析:读写屏障(Read/Write Barrier)

在JVM垃圾回收体系中,三色标记并发漏标问题 是所有并发GC的核心痛点。而解决漏标、保证并发标记数据一致性的核心底层机制,就是读屏障(Read Barrier)写屏障(Write Barrier)

很多开发者只知道「G1用写屏障、ZGC用读屏障」,但完全不清楚屏障的源码实现逻辑、触发时机、底层开销、优缺点与使用限制

一、屏障核心前置背景:为什么需要读写屏障?

1.1 并发GC致命问题:对象漏标

三色标记并发标记阶段,业务线程与GC线程并行执行,会出现存活对象被误标记为垃圾的漏标问题,必须同时满足两个条件:

  1. 条件一:灰色对象删除了白色对象的唯一引用(断开旧引用)。

  2. 条件二:黑色对象新增了白色对象的引用(建立新引用)。

一旦触发,白色存活对象无法被GC扫描到,最终被错误回收,引发程序逻辑错乱、空指针、数据异常。

1.2 屏障的核心定位

读写屏障是JVM植入在「对象引用读写指令前后」的一段拦截代码,属于字节码层+机器码层的增强逻辑,并非Java代码实现。

核心作用:拦截并发引用变更,修正三色标记状态,杜绝漏标,保证并发GC数据一致性

简单总结行业通用设计:

  • CMS / G1 :依赖 写屏障 解决漏标问题。

  • ZGC / Shenandoah :依赖 读屏障 解决漏标、并发转移、指针失效问题。


二、写屏障(Write Barrier)深度解析------CMS/G1核心机制

2.1 写屏障核心定义

写屏障 :JVM在对象引用赋值、修改、引用替换操作时,自动插入的一段前置/后置拦截逻辑。

只要发生obj.field = newObj 引用写操作,就会触发写屏障,拦截引用变更行为,记录跨代引用或修正标记状态。

写屏障是 「基于写操作的主动记录机制」,核心服务于CMS、G1的卡表、记忆集、并发标记修正。

2.2 写屏障底层源码实现(HotSpot)

HotSpot 中所有引用赋值操作,最终都会调用 oop_store 底层函数,写屏障逻辑内嵌其中(src/share/oops/oop.inline.hpp)。

cpp 复制代码
// HotSpot底层引用赋值核心源码
template <class T>
inline void oop_store(T* p, oop v) {
  // 1. 原始引用赋值
  *p = v;
  // 2. 写入完成后触发【写屏障】核心逻辑
  if (UseConcMarkSweepGC || UseG1GC) {
    // 卡表标记、跨代引用记录、并发标记追踪
    write_barrier(p, v);
  }
}

针对G1/CMS,写屏障核心分为两个分支:

  1. 卡表写屏障:维护跨代引用,标记脏卡,用于MinorGC快速扫描。

  2. 并发标记写屏障:记录黑色对象新增白色引用,用于解决并发漏标。

2.3 写屏障Java代码示例

如下普通引用赋值代码,底层会触发JVM写屏障拦截:

cpp 复制代码
public class BarrierDemo {
    // 老年代对象
    private static OldObj oldObj = new OldObj();

    public void writeBarrierTest() {
        // 【触发写屏障】老年代对象新增新生代白色对象引用
        oldObj.child = new YoungObj();
    }
}

代码看似简单赋值,JVM底层自动完成:

  1. 拦截引用写入操作;

  2. 判断是否跨代引用;

  3. 更新卡表、标记脏卡;

  4. 并发标记阶段记录新增引用,防止漏标。

2.4 写屏障工作流程(G1/CMS)

  1. 监听写操作:拦截所有对象引用赋值、替换、清空操作。

  2. 跨代引用判断:如果老年代引用指向新生代,标记对应卡页为「脏卡」。

  3. 漏标预防:并发标记期间,黑色对象新增白色引用,将白色对象推入标记栈。

  4. 后置修正:重新标记阶段统一处理所有屏障记录的变动引用,完成修正。

2.5 写屏障核心优点

  • 精准拦截、开销低:仅在引用写入时触发,读对象无任何开销,日常业务读取远大于写入,整体性能损耗极小。

  • 完美维护跨代引用:配合卡表记忆集,彻底解决分代GC全堆扫描问题,是分代回收的基石。

  • 稳定可靠:CMS/G1多年工业级验证,无严重BUG、稳定性极强。

  • 可预测性强:屏障开销固定,不会随堆内存增大而飙升。

2.6 写屏障缺点与限制

  • 必须依赖STW重新标记:写屏障仅能记录引用变动,无法实时修正,必须依赖短暂STW完成最终修正,无法做到完全无停顿。

  • 存在卡表维护开销:需要持续维护卡表、记忆集数据结构,大内存场景堆外内存开销递增。

  • 无法处理并发对象转移:写屏障只能解决标记漏标,不能处理对象移动后的指针失效问题,无法支撑极致并发转移。

  • 浮动垃圾无法规避:并发阶段新产生的垃圾,本轮GC无法回收,只能等待下一轮。


三、读屏障(Read Barrier)深度解析------ZGC/Shenandoah核心机制

3.1 读屏障核心定义

读屏障 :JVM在对象引用读取、访问时触发的拦截逻辑,只要业务线程从堆中读取对象引用,就会被读屏障拦截校验。

读屏障是 基于读操作的实时修正机制,不依赖卡表、不依赖STW重新标记,是ZGC实现亚毫秒级停顿的核心底牌。

3.2 读屏障底层源码核心逻辑(ZGC)

ZGC 所有堆引用读取,都会经过 ZBarrier::load_barrier 读屏障拦截(src/hotspot/gc/z/zBarrier.cpp)。

cpp 复制代码
// ZGC读屏障核心伪源码
oop ZBarrier::load_barrier(oop ptr) {
  // 1. 校验指针颜色标记位
  if (ptr.is_marked()) {
    return ptr;
  }
  // 2. 对象已转移、指针失效,实时修复重映射
  if (ptr.is_relocated()) {
    return relocate_object(ptr);
  }
  // 3. 正常返回有效引用
  return ptr;
}

核心逻辑:读到即修正、实时兜底、无需全局扫描、无需STW

3.3 读屏障Java代码示例

任意对象读取操作,都会触发ZGC读屏障:

cpp 复制代码
public class ZgcBarrierDemo {
    public void readBarrierTest() {
        // 【触发读屏障】从堆读取引用
        User user = userObj;
        // 再次读取、访问属性,持续校验指针有效性
        System.out.println(user.getName());
    }
}

每一次堆引用读取,底层自动校验:指针是否失效、对象是否转移、是否需要重映射,实时修复数据。

3.4 读屏障工作流程(ZGC)

  1. 拦截读操作:拦截所有堆对象引用读取行为。

  2. 校验颜色指针状态:判断指针处于标记、转移、重映射哪种状态。

  3. 实时修正失效指针:如果对象已经并发转移,自动返回新对象地址。

  4. 更新标记状态:动态修正三色标记,彻底杜绝漏标。

3.5 读屏障核心优点

  • 彻底告别STW修正:无需重新标记阶段大范围STW,修正逻辑全部并发完成,停顿时间极致压缩。

  • 无卡表开销:依托颜色指针存储标记,无需维护卡表、记忆集,超大内存扩展性极强。

  • 支持全程并发对象转移:唯一可以支撑「边运行业务、边迁移对象」的屏障机制,彻底解决内存碎片。

  • 零漏标、零错标:读取即修正,从根源杜绝并发标记问题。

3.6 读屏障缺点与限制

  • 全局读开销:业务读取操作远多于写入,所有读操作都要走屏障校验,存在固定CPU损耗。

  • 吞吐量略低:相比G1写屏障,高吞吐批量任务场景性能稍弱。

  • 强依赖64位系统:读屏障+颜色指针依赖64位指针高位空闲位,32位系统无法支持。

  • 早期无分代回收短板:JDK21之前无分代,短期对象回收不高效,进一步影响吞吐量。


四、读写屏障核心差异与底层限制总对比

对比维度 写屏障(Write Barrier) 读屏障(Read Barrier)
触发时机 对象引用写入、修改、赋值时触发 对象引用读取、访问时触发
归属收集器 CMS、G1 ZGC、Shenandoah
核心能力 记录引用变更、维护卡表、预防漏标 实时修正指针、并发转移、杜绝漏标
STW依赖 依赖重新标记STW完成最终修正 完全不依赖STW,全程并发修正
内存开销 高(需维护卡表/记忆集) 极低(依托指针着色,无额外结构)
CPU开销 低(仅写操作触发) 较高(所有读操作触发)
并发转移支持 不支持 完美支持
最大限制 无法彻底消除STW、存在内存碎片、有浮动垃圾 读开销高、吞吐量受限、依赖64位架构
场景定位 平衡吞吐与延迟、通用场景 极致低延迟、大内存高并发场景

五、高频面试核心总结

  1. 为什么G1用写屏障? 业务读多写少,写屏障开销更低、吞吐量更高,配合卡表实现分代高效回收。

  2. 为什么ZGC敢用读屏障? 为了彻底消除STW、支持并发对象转移,牺牲部分吞吐量换取极致低延迟。

  3. 漏标解决方案差异:写屏障是「先记录、后STW修正」;读屏障是「边读取、边实时修正」。

  4. 屏障本质:JVM底层植入的拦截指令,不属于Java代码,是JIT编译阶段的机器码增强。

  5. 硬性限制:写屏障受限于卡表与STW,读屏障受限于CPU读开销与系统架构。


六、全文总结

写屏障是传统分代GC的最优解,以极小的写入开销、配合卡表记忆集,实现了吞吐量与延迟的平衡,是CMS、G1稳定运行的底层基石,缺点是无法摆脱STW与内存碎片问题。

读屏障是现代超低延迟GC的革新方案,放弃卡表体系、依托颜色指针与实时修正,彻底打破STW瓶颈,支撑ZGC实现亚毫秒级GC停顿,代价是牺牲部分读吞吐量。

读写屏障的取舍,本质就是JVM垃圾收集器的吞吐与延迟的终极取舍追求稳定高吞吐选写屏障体系(G1),追求极致低延迟选读屏障体系(ZGC)

相关推荐
wuminyu2 小时前
Java世界中StringTable源码剖析
java·linux·c语言·jvm·c++
醉颜凉4 小时前
Elasticsearch性能优化:JVM GC调优全攻略,彻底解决集群卡顿、吞吐量下降问题
jvm·elasticsearch·性能优化
顺风尿一寸5 小时前
从 Java 到内核:探秘线程改名的完整路径
jvm
lihao lihao7 小时前
linux线程
java·开发语言·jvm
南极企鹅1 天前
JVM-编译执行过程
jvm
苏克贝塔1 天前
.NET开发之.net framework对比.net core
jvm
cfm_29141 天前
JVM垃圾收集算法与收集器深度解析
jvm·测试工具·算法·性能优化
自律懒人1 天前
AI Agent 工作流编排实战:从单 Agent 到多 Agent,手搭一套能跑通的协作系统
jvm
石一峰6991 天前
SQLite 与 db_manager 集成关键概念详解
jvm·数据库·sqlite