volatile写插入StoreStore+StoreLoad屏障,读插入LoadLoad+LoadStore屏障,由JVM JIT编译器依JMM和CPU架构自动注入,确保可见性与禁止重排序。

Java 中 volatile 变量的读写操作,会在编译后由 JVM 在生成的字节码和最终的本地机器指令层面,自动插入相应的内存屏障(Memory Barrier),以确保可见性和禁止重排序。这些屏障不是 Java 代码里显式写的,而是 JVM 根据 Java 内存模型(JMM)规范,在 JIT 编译阶段根据平台特性(如 x86、ARM)自动注入的。
volatile 写操作插入的内存屏障
对 volatile 变量执行写操作(如 x = 1)时,JVM 保证:
- 写操作本身不会被重排序到其后的任何读/写之前;
- 写入的值对其他线程立即可见(需配合缓存一致性协议,如 x86 的 MESI)。
在底层,JVM 通常会插入:
- StoreStore 屏障:确保该 volatile 写之前的普通写操作,不会重排到它之后;
-
StoreLoad 屏障(x86 上通常用
lock addl $0,0(%rsp)或mfence指令模拟):防止该 volatile 写与后续的读/写重排序——这是开销最大的屏障,但 x86 因其强内存模型,实际常通过一个带lock前缀的空操作(如lock; addl $0,0)来实现 StoreLoad 语义。
volatile 读操作插入的内存屏障
对 volatile 变量执行读操作(如 int v = x)时,JVM 保证:
- 该读操作不会被重排序到其前面的任何读/写之后;
- 读取的是最新写入的值(即能看到之前所有线程对这个变量的写)。
底层通常插入:
- LoadLoad 屏障:确保该 volatile 读之前的普通读不会重排到它之后;
- LoadStore 屏障:确保该 volatile 读不会重排到其后的普通写之前。
x86 平台上,volatile 读一般不需要额外指令(因为 x86 的读天然具有 LoadLoad + LoadStore 语义),但 JIT 编译器仍可能插入一个轻量级序列化指令(如 mov 后加 lfence,或依赖 cache line 读本身的顺序性)来满足 JMM 要求;ARM 等弱序平台则必须显式插入 dmb ish 等屏障指令。
立即学习“Java免费学习笔记(深入)”;
JVM 如何决定插入哪些屏障
屏障的插入由 JVM 的 JIT 编译器(如 HotSpot 的 C2 编译器)在生成本地代码时完成,依据是:
- Java 字节码中的
getstatic/putstatic或getfield/putfield指令是否标记为 volatile; - 目标 CPU 架构的内存模型特性(x86 强序 → 屏障更少;ARM/PowerPC 弱序 → 需更多 dmb/barrier 指令);
- 编译器优化阶段的重排序分析结果(例如逃逸分析、字段访问模式等会影响是否真正生成屏障)。
你可以通过 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(配合 hsdis)查看 HotSpot 生成的汇编,其中能看到类似 lock addl $0,0(%rsp)(volatile 写)、mov 加 lfence 或 dmb ish(ARM)等指令。
不依赖具体指令,但必须满足 JMM 语义
JVM 规范并不要求使用某条特定 CPU 指令,只要最终效果满足 JMM 对 volatile 的定义即可:
- 写 volatile 变量 → 全局可见 + happens-before 于后续任意线程对该变量的读;
- 读 volatile 变量 → 获取最新值 + happens-before 于后续任意操作。
因此,“插入什么指令”本质是 JVM 实现细节,不同版本、不同平台可能不同。开发者只需按 JMM 语义编码,不必也不应手动干预屏障——否则反而破坏可移植性和正确性。


















