面试知识库
进阶

Java内存模型基础#

一句话答案#

JMM 规定线程的工作内存与主内存的交互规则,通过 volatile/synchronized/final 保证可见性、有序性和原子性。

核心要点

JMM 抽象模型:

  • 主内存存共享变量,每个线程有自己的工作内存(对 CPU 寄存器/缓存的抽象)。线程只能操作工作内存中的副本,再同步回主内存。
  • 为什么需要这层抽象?为了屏蔽底层差异:不同 CPU 的缓存一致性协议、指令重排序、写缓冲区各不相同,JMM 用一套统一规则(happens-before)让程序员不必关心硬件细节就能写出正确的并发程序。注意:JMM 是抽象规范,不是堆/栈/方法区那种实际内存划分。

三大特性:

  • 原子性:操作不可分割。基本读写原子(long/double 在 32 位机非 volatile 时可能被拆成两次),i++ 不原子;靠 synchronized/Lock/CAS 保证。
  • 可见性:一个线程的写对其他线程及时可见。靠 volatilesynchronizedfinal 保证。
  • 有序性:禁止有害的指令重排序。靠 volatilesynchronized 及 happens-before 保证。

happens-before 规则(JMM 的核心): 若 A happens-before B,则 A 的结果对 B 可见且 A 排在 B 前。无需关心底层重排,只要满足 hb 就保证正确:

  • 程序次序规则:单线程内前面的操作 hb 后面的操作。
  • 监视器锁规则:对一个锁的 unlock hb 后续对同一锁的 lock
  • volatile 规则:对 volatile 变量的写 hb 后续对它的读。
  • 传递性:A hb B 且 B hb C,则 A hb C(很多正确性推导靠它串起来,如 DCL)。
  • 还有线程启动(start() hb 线程内动作)、线程终止(线程内动作 hb join() 返回)、中断、对象终结等规则。

内存屏障如何落地(深挖点): JMM 用四类屏障禁止重排、强制刷主内存:

屏障含义
LoadLoad屏障前的读 先于 屏障后的读
StoreStore屏障前的写 先于 屏障后的写
LoadStore屏障前的读 先于 屏障后的写
StoreLoad屏障前的写 先于 屏障后的读(开销最大,全能屏障)
  • volatile 写:前插 StoreStore、后插 StoreLoad —— 保证写之前的操作不被排到写之后,且写完立刻刷主内存。
  • volatile 读:后插 LoadLoad + LoadStore —— 保证读到最新值,且读之后的操作不被排到读之前。
  • as-if-serial:无论怎么重排,单线程的执行结果不能变(编译器/CPU 优化的底线);多线程下才需要 happens-before 额外约束。

注:本文讲清模型即可,volatile/锁的可见性有序性如何由 hb 推导、DCL 等实战,见关联深入篇 JMM与happens-before

面试回答(2分钟版)

JMM 即 Java 内存模型,是 JVM 规范中定义的多线程环境下共享变量访问规则,它规定每个线程有自己的工作内存,共享变量存放在主内存中,线程对变量的操作必须在工作内存中进行。JMM 要解决三个核心问题:可见性是指一个线程修改了共享变量另一个线程能否及时看到,volatile 通过写时刷新到主内存、读时从主内存加载来保证可见性;有序性是指编译器和 CPU 可能对指令重排序,在单线程中不影响结果但多线程下可能看到中间状态,volatile 和 synchronized 通过内存屏障禁止特定重排序;原子性是指操作不可分割,synchronized 通过加锁保证原子性,但 volatile 不保证原子性,比如 i++ 仍然不是原子操作。需要注意 JMM 和 JVM 内存结构是不同概念,JMM 是规范层面定义可见性和有序性的抽象模型,JVM 内存结构是堆、栈、方法区这些实际的内存区域划分。JMM 真正的核心是 happens-before 规则——程序次序、锁的 unlock 先于后续 lock、volatile 写先于读、以及传递性,只要满足这些关系就保证跨线程可见和有序,底层则由四类内存屏障(LoadLoad/StoreStore/LoadStore/StoreLoad)落地。

追问与易错

追问方向:

  • “JMM 和 JVM 内存结构什么关系?”→ JMM 是 JVM 规范中定义多线程可见性/有序性/原子性的抽象模型;JVM 内存结构是堆、栈、方法区等实际内存区域划分,二者是不同层面的概念
  • “volatile 怎么保证可见性的?”→ 写 volatile 变量时强制刷新工作内存到主内存,读时强制从主内存加载,通过插入内存屏障(StoreStore/StoreLoad/LoadLoad/LoadStore)实现
  • “什么是指令重排?什么情况下会有问题?”→ 编译器和 CPU 为优化性能对指令重新排序,单线程下 as-if-serial 保证结果不变;多线程下可能看到对象未完全初始化等中间状态(如 DCL 单例的经典问题)

易错点:

  • ❌ 混淆 JMM(Java Memory Model)和 JVM 内存结构(堆/栈/方法区)
  • ❌ “volatile 能保证原子性”——只保证可见性和有序性,i++ 仍不是原子的