JMM Java 内存模型(Java Memory Model)

📅 2026/7/13 13:33:00 👁️ 阅读次数 📝 编程学习
JMM Java 内存模型(Java Memory Model)

目录

一、JMM 是什么

核心分层模型(主内存 + 工作内存)

二、并发三大特性 & JMM 对应的问题

1. 可见性 Visibility

2. 原子性 Atomicity

3. 有序性 Ordering

三、JMM 核心:happens-before 先行发生原则

四、内存屏障(volatile 底层实现)

五、synchronized 在 JMM 的语义

六、volatile 关键特性总结

DCL 单例为什么必须加 volatile

七、JMM 与 CPU 缓存一致性

八、易混淆区分

九、高频面试精简考点


一、JMM 是什么

JMM 是一套抽象规范,定义了多线程环境下:

  1. 线程如何访问共享变量
  2. 变量在主内存、工作内存之间的读写交互规则
  3. 可见性、原子性、有序性三大并发问题解决方案注意:JMM ≠ JVM 运行时数据区(堆 / 栈 / 方法区),它是逻辑模型,不是物理内存划分。

核心分层模型(主内存 + 工作内存)

  1. 主内存(Main Memory)共享变量存放处,所有线程共享,对应堆中实例字段、静态变量。
  2. 工作内存(Working Memory)每个线程私有,线程操作变量只能拷贝副本到工作内存读写,不能直接操作主内存。 对应虚拟机栈局部变量、寄存器、CPU 缓存。

交互流程:

主内存 <--(读写同步)--> 线程工作内存(副本)

线程修改变量步骤:

  1. 从主内存拷贝变量副本到工作内存
  2. 在工作内存读写副本
  3. 刷新副本写回主内存(时机不固定,无同步会延迟)

二、并发三大特性 & JMM 对应的问题

1. 可见性 Visibility

问题:A 线程修改副本后没立刻刷回主内存,B 线程看不到最新值。 解决方案:volatilesynchronizedLock

2. 原子性 Atomicity

问题:多步操作被打断(如i++= 读 + 加 + 写,三步非原子),多线程交错导致数据错乱。 解决方案:synchronizedLockAtomic原子类

3. 有序性 Ordering

问题:CPU / 编译器指令重排,代码书写顺序和实际执行顺序不一致,多线程出现诡异 bug。 重排三种:编译器重排、CPU 指令重排、内存屏障重排。 解决方案:volatile(禁止前后重排)、synchronizedhappens-before

三、JMM 核心:happens-before 先行发生原则

不用锁 /volatile 也能天然保证有序、可见,8 条规则:

  1. 程序顺序规则:单线程内,前面代码 happens-before 后面代码
  2. 监视器锁规则:unlock 锁 happens-before 后续同一个 lock 锁
  3. volatile 变量规则:对 volatile 写 happens-before 后续对该变量读
  4. 线程启动规则start()happens-before 线程内所有操作
  5. 线程终止规则:线程内所有操作 happens-beforejoin()返回
  6. 中断规则interrupt()调用 happens-before 检测中断代码
  7. 对象终结规则:对象初始化完成 happens-beforefinalize()
  8. 传递性:A hb B,B hb C → A hb C

只要满足 happens-before,前操作对后操作可见、有序

四、内存屏障(volatile 底层实现)

volatile 通过插入内存屏障禁止重排,四种屏障:

  1. LoadLoad:读 1,LoadLoad,读 2 → 读 1 先执行完再读 2
  2. StoreStore:写 1,StoreStore,写 2 → 写 1 刷新主存再写 2
  3. LoadStore:读 1,LoadStore,写 2 → 读完再写
  4. StoreLoad:写 1,StoreLoad,读 2(开销最大,volatile 写后必加)

volatile 读写屏障规则:

  • 写 volatile:前面所有读写不能重排到写之后;写完插入 StoreLoad 屏障
  • 读 volatile:后面所有读写不能重排到读之前

五、synchronized 在 JMM 的语义

  1. 原子性:临界区代码同一时间仅一个线程执行
  2. 可见性
    • 加锁 lock:清空工作内存副本,从主内存重新加载变量
    • 解锁 unlock:把工作内存修改全部刷新回主内存
  3. 有序性:临界区内代码不会和外部指令跨锁重排,满足监视器 hb 规则

六、volatile 关键特性总结

  1. 保证可见性:写后立刻刷主存,读时强制从主存加载
  2. 保证有序性:禁止指令重排
  3. 不保证原子性:复合操作(i++)依旧线程不安全 适用场景:状态标记、双重检查锁 DCL 单例

DCL 单例为什么必须加 volatile

不加 volatile 时,对象创建三步会被重排:

  1. 分配内存
  2. 引用赋值给 instance
  3. 初始化对象 重排后顺序 1→2→3,其他线程拿到半初始化的 instance,出现空指针 / 脏数据。 volatile 禁止 2、3 重排,解决问题。

七、JMM 与 CPU 缓存一致性

现代 CPU 多级缓存 (L1/L2/L3),硬件层面有缓存一致性协议 (MESI),但硬件开销大。 JMM 在语言层面屏蔽不同 CPU 硬件差异,统一一套并发内存语义,让 Java 跨平台并发行为一致。

八、易混淆区分

  1. JMM:语言内存模型,并发规范,解决多线程可见 / 原子 / 有序
  2. JVM 运行时数据区:堆、虚拟机栈、本地方法栈、程序计数器、元空间,是内存物理划分
  3. 硬件内存模型:CPU 缓存、MESI 缓存协议,底层硬件实现

九、高频面试精简考点

  1. i++ 线程不安全原因:无原子性、无可见性、指令重排
  2. volatile 和 synchronized 区别:volatile 轻量,无原子;synchronized 独占锁,三者都保证
  3. happens-before 是什么:JMM 的有序可见判断依据
  4. 内存屏障作用:阻止指令重排,强制刷新缓存
  5. DCL 单例 volatile 作用:防止对象初始化指令重排