Java内存模型(JMM)是定义多线程下可见性、有序性、原子性的抽象规范,划分主内存与工作内存逻辑概念,通过happens-before规则约束执行顺序,而非描述JVM物理内存结构。

Java内存模型不是JVM内存结构
很多人一看到“Java内存模型”就去翻JVM堆、栈、方法区的图,这是错的。JMM(Java Memory Model)是抽象规范,不描述物理内存布局,只定义volatile、synchronized、final等关键字在多线程下如何保证可见性、有序性、原子性。
它把内存划分为「主内存」和「工作内存」两个逻辑概念:
- 主内存:所有线程共享,变量初始值和最终结果都存在这里
- 工作内存:每个线程私有,线程操作变量前必须先从主内存拷贝副本到自己的工作内存中
注意:这里的“工作内存”不是JVM运行时数据区里的任何一块真实内存,它可能对应寄存器、CPU缓存,甚至部分在栈帧里——具体实现由JVM决定,但行为必须符合JMM约束。
变量读写必须经过主内存中转
线程不能直接读写主内存中的变量,所有操作都要走“读取→拷贝→计算→写回”流程。比如对一个普通int变量做i++,实际包含三步:
立即学习“Java免费学习笔记(深入)”;
- 从主内存读取
i的值,加载到当前线程的工作内存 - 在工作内存中执行加1操作
- 把结果写回主内存
这三步不是原子的。如果两个线程同时执行i++,很可能都读到旧值,各自加1后写回,导致最终只+1而不是+2。
解决办法不是靠“避免并发”,而是用JMM提供的同步机制强制刷新:
- 用
synchronized块包裹操作:进入时清空工作内存,从主内存重新读;退出时把修改强制刷回主内存 - 声明变量为
volatile:每次读都从主内存取,每次写都立即刷回主内存(但不保证复合操作原子性)
happens-before规则才是JMM落地的关键
你写的代码顺序 ≠ 实际执行顺序。编译器、JVM、CPU都可能重排序,只要不破坏单线程语义。而JMM通过happens-before规则来约束多线程下的可见性边界。
常见happens-before关系包括:
- 程序次序规则:同一个线程内,前面的语句
happens-before后面的语句(注意:不是字节码顺序,而是控制流意义上的先后) - 监视器锁规则:对同一个锁,unlock操作
happens-before后续对该锁的lock操作 -
volatile变量规则:对一个volatile变量的写操作happens-before后续对该变量的读操作 - 线程启动规则:
Thread.start()调用happens-before子线程的任意动作
没有happens-before关系的操作,就可能被重排,也可能看不到最新值。别指望“我先写了,你后读就一定看到”——没规则保障,就是没保障。
容易被忽略的陷阱:final字段的特殊语义
final不只是“不可变”,它在JMM中有明确的发布语义。构造器中对final字段的写入,与对象引用的赋值之间存在happens-before关系。
这意味着:只要对象是正确构造的(没发生this逃逸),其他线程通过合法途径拿到该对象引用,就一定能看到final字段的正确初始化值。
但要注意:
- 这个保障仅限
final字段本身,不递归延伸到其引用的对象内部状态 - 如果构造过程中把
this泄露出去(比如注册监听器、开新线程),上述保障失效 -
static final基本类型还有更早的初始化时机优化,但引用类型仍依赖类初始化过程的同步语义
JMM的复杂性不在概念多,而在“哪些地方隐含了同步契约”。写并发代码时,盯着变量怎么读写没用,得盯住有没有建立happens-before链。漏掉一环,就可能在高并发下跑出难以复现的问题。









