Java 内存模型 JMM 是什么?

2025年 阅读约 10 分钟 面试指南 · Java面试 · Java并发

深入解析Java内存模型JMM:主内存与工作内存的抽象、8条happens-before规则详解、volatile/synchronized/final的内存语义、并发编程三大特性(原子性/可见性/有序性),附完整面试模拟。

一句话总结

JMM(Java Memory Model)是 Java 虚拟机规范中定义的并发编程内存模型,屏蔽了不同硬件和操作系统的内存访问差异。核心是主内存与工作内存的抽象:所有变量存在主内存,线程操作变量时先从主内存拷贝到工作内存。JMM 围绕原子性、可见性、有序性三大特性,通过 8 条 happens-before 规则来约束编译器和处理器的优化行为,保证多线程程序的正确性。

初级理解

JMM 是什么?

JMM 不是真实存在的物理内存,而是一个抽象概念。它定义了 Java 程序中变量(实例字段、静态字段、数组元素)的访问规则。

主内存 vs 工作内存

概念对应物理说明
主内存物理内存(RAM)所有变量存储的地方,线程共享
工作内存CPU 缓存/寄存器每个线程私有,存储变量的副本

线程不能直接操作主内存中的变量,必须先拷贝到工作内存,修改后再写回主内存。这就导致了可见性问题。

并发编程三大特性

特性定义实现方式
原子性一个操作不可中断,要么全做要么全不做synchronized、Lock、CAS
可见性一个线程修改后,其他线程立即可见volatile、synchronized、final
有序性程序按代码顺序执行(禁止重排序)volatile、synchronized
一句话总结:JMM = 主内存 + 工作内存 + happens-before 规则,解决并发编程的原子性、可见性、有序性问题。

中级深入

8 条 happens-before 规则

happens-before 是 JMM 的核心规则。如果操作 A happens-before 操作 B,那么 A 的结果对 B 可见,且 A 的执行顺序在 B 之前

#规则说明
1程序次序规则同一线程内,前面的操作 happens-before 后面的操作
2管程锁定规则unlock happens-before 后续的 lock(同一把锁)
3volatile 规则volatile 写 happens-before 后续的 volatile 读
4线程启动规则Thread.start() happens-before 线程中的任何操作
5线程终止规则线程中所有操作 happens-before Thread.join() 返回
6线程中断规则interrupt() happens-before 被中断线程检测到中断
7对象终结规则构造函数结束 happens-before finalize()
8传递性A hb B, B hb C → A hb C

volatile 的内存语义

// volatile 写:类似释放锁,将工作内存刷新到主内存 volatileVar = 1; // volatile 读:类似获取锁,从主内存重新读取 int v = volatileVar; // volatile 的 happens-before 传递性 int a = 0; volatile boolean flag = false; // 线程 A: a = 1; // ① 普通写 flag = true; // ② volatile 写 // 线程 B: if (flag) { // ③ volatile 读 int x = a; // ④ 普通读 } // ① hb ②(程序次序) + ② hb ③(volatile规则) + ③ hb ④(程序次序) // → ① hb ④(传递性)→ x 一定等于 1

synchronized 的内存语义

synchronized 的内存语义:进入同步块时清空工作内存中变量的值,从主内存重新读取;退出同步块时将工作内存中的修改刷新到主内存。

final 的内存语义

final 域的内存语义:在构造函数中对 final 域的写入,与随后通过该引用访问该 final 域之间,存在 happens-before 关系。这保证了 final 域在构造函数完成后对所有线程可见。

中级要点:happens-before 是判断数据是否存在竞争的依据。volatile 写 hb volatile 读,synchronized 解锁 hb 加锁,传递性保证可见性。

高级拓展

JMM 与硬件内存模型的关系

JMM 屏蔽了不同 CPU 架构的内存模型差异。x86 是强内存模型(TSO),只允许 Store-Load 重排序;ARM/PowerPC 是弱内存模型,允许更多重排序。JMM 通过内存屏障在不同平台上实现一致的语义。

双重检查锁定(DCL)为什么需要 volatile?

从 JMM 角度:没有 volatile 时,new 操作(分配内存、初始化、赋值引用)可能重排序,导致其他线程看到未初始化完成的对象。volatile 禁止了这种重排序。

JSR-133 对 JMM 的修复

JDK 5 之前的 JMM 存在严重缺陷(如 final 域可能被修改)。JSR-133 重新定义了 JMM,增强了 final 和 volatile 的语义。JDK 5+ 的 JMM 才是可靠的。

高级加分项:能说出 JMM 屏蔽硬件差异的设计目的,知道 x86 和 ARM 内存模型的区别,了解 JSR-133 对 JMM 的修复。

实战场景

场景一:利用 volatile 的 happens-before 传递性

// 利用 volatile 保证多个变量的可见性 volatile boolean ready = false; int data = 0; // 写线程 data = 42; // 普通写 ready = true; // volatile 写(保证 data 的修改也可见) // 读线程 if (ready) { // volatile 读 // data 一定等于 42(happens-before 传递性) System.out.println(data); }

场景二:安全发布对象

// 不安全发布:其他线程可能看到未初始化的对象 public class UnsafeLazyInit { private static Resource instance; public static Resource getInstance() { if (instance == null) { instance = new Resource(); // 可能重排序! } return instance; } } // 安全发布方式: // 1. 加 volatile private static volatile Resource instance; // 2. 用静态内部类(利用类加载的线程安全性) private static class Holder { static final Resource INSTANCE = new Resource(); }

面试模拟

Q:JMM 是什么?

A:JMM 是 Java 虚拟机规范定义的并发内存模型,核心是主内存与工作内存的抽象。围绕原子性、可见性、有序性三大特性,通过 happens-before 规则约束编译器和 CPU 的优化,保证多线程程序的正确性。volatile、synchronized、final 都有各自的内存语义。

Q:happens-before 规则有哪些?

A:8 条规则:1) 程序次序(单线程内顺序);2) 管程锁定(unlock hb lock);3) volatile(写 hb 读);4) 线程启动(start hb 线程内操作);5) 线程终止(线程内操作 hb join);6) 线程中断(interrupt hb 检测中断);7) 对象终结(构造 hb finalize);8) 传递性(A hb B, B hb C → A hb C)。

Q:volatile 的 happens-before 规则有什么用?

A:volatile 写 happens-before volatile 读。结合传递性,volatile 写之前的所有操作的结果,对 volatile 读之后的所有操作都是可见的。这就是 DCL 单例中 volatile 能保证对象完全初始化的原因。