08 - Java 内存模型与并发
定位: Java 内存模型是连接 Java 并发编程与硬件内存模型的桥梁——定义了一组规则,让程序员写出可预期的多线程代码,同时为 JVM 实现者保留优化空间。 面试高频度: ⭐⭐⭐⭐⭐
一、为什么需要 JMM?
硬件现实:CPU 不是按直觉工作的
现代 CPU 为了性能,做了三件"破坏直觉"的事:
CPU Core 0 CPU Core 1
┌──────────────────┐ ┌──────────────────┐
│ 寄存器 │ │ 寄存器 │
├──────────────────┤ ├──────────────────┤
│ L1 Cache (快) │ │ L1 Cache (快) │
├──────────────────┤ ├──────────────────┤
│ L2 Cache │ │ L2 Cache │
├──────────────────┤ ├──────────────────┤
│ Store Buffer │ │ Store Buffer │← 写操作先写这里(异步!)
└────────┬─────────┘ └────────┬─────────┘
│ │
└──────────L3 Cache───────────┘
│
┌─────┴─────┐
│ Main Memory │
└──────────────┘三个硬件优化:
- CPU Cache:每个核心有自己的 L1/L2 缓存。Thread A 的写可能只停留在 A 的 L1 中,Thread B 读不到。
- Store Buffer:写操作先进入 Store Buffer(异步),批量刷入缓存。其他核心在写入缓存前看不到新值。
- 指令重排序:CPU + 编译器都会重排序——只要不改变单线程执行结果。但多线程角度看,顺序可能完全错乱。
Consequence:没有 JMM 规则,x=1; y=2 可能被观察为 y=2; x=0(Store Buffer 延迟)。多线程程序的正确性完全不可预期。
JMM 的角色
JMM 不是一个"真实的内存层",而是一组抽象规则——定义了线程何时能看到共享变量的最新值(可见性),以及哪些顺序保证成立(有序性)。
硬件混乱的现实 JMM 抽象规则 Java 程序员眼中的世界
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ CPU 重排序 │────→│ happens-before │────→│ "看起来像顺序一 │
│ Store Buffer │ │ volatile 语义 │ │ 致性" │
│ Cache 不一致 │ │ final 语义 │ │ (实际上编译器 │
│ 编译器优化 │ │ synchronized 规则 │ │ 在规则内重排序) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
混乱 有规则 可预期二、子专题导航
| # | 主题 | 面试频率 | 核心内容 |
|---|---|---|---|
| **JMM与happens-before** | JMM 与 happens-before | ⭐⭐⭐⭐⭐ | JMM 本质、8 条 happens-before 规则、可见性保证的本质 |
| **volatile的内存语义** | volatile 内存语义 | ⭐⭐⭐⭐⭐ | 可见性、禁止重排序、内存屏障、DCL 问题 |
| **final的内存语义** | final 内存语义 | ⭐⭐⭐⭐ | JSR-133 修复、构造函数逃逸、不可变保证范围 |
| **synchronized的JVM实现** | synchronized 底层实现 | ⭐⭐⭐⭐⭐ | monitorenter/monitorexit、ObjectMonitor、MarkWord 锁状态 |
| **锁升级过程** | 锁升级完整过程 | ⭐⭐⭐⭐⭐ | 偏向锁→轻量锁→重量锁、MarkWord 变换、批量重偏向/撤销 |
三、核心概念全景
┌─────────────────────────────────────────────────────────────────────────┐
│ JMM 体系全景图 │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ Java 内存模型 (JMM) │ │
│ │ │ │
│ │ 核心目标: 定义"线程看到共享变量"的规则 │ │
│ │ ├─ 可见性 (Visibility) — 线程 A 的写何时对线程 B 可见 │ │
│ │ ├─ 有序性 (Ordering) — 重排序在哪些边界内是安全的 │ │
│ │ └─ 原子性 (Atomicity) — 哪些操作是原子的(JMM 保证 8 字节对齐读写) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────┐ ┌────────────┐ │
│ │ happens-before │ │ volatile │ │ final │ │
│ │ 8 条规则 │ │ 内存屏障 │ │ 构造函数安全 │ │
│ │ 可见性契约 │ │ DCL 修复 │ │ 发布安全 │ │
│ └──────────────────┘ └──────────┘ └────────────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ synchronized 实现机制 │ │
│ │ ├─ MarkWord 锁状态位 │ │
│ │ ├─ ObjectMonitor │ │
│ │ └─ 锁升级: 偏向 → 轻量 → 重量 │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘四、核心考点速记
1. happens-before 8 条规则(最快速度背下来)
| # | 规则 | 一句话 |
|---|---|---|
| 1 | 程序次序规则 | 单线程内,写在前面的代码 happens-before 后面的 |
| 2 | 监视器锁规则 | unlock hb 后续 lock |
| 3 | volatile 变量规则 | volatile 写 hb 后续 volatile 读 |
| 4 | 传递性 | A hb B ∧ B hb C → A hb C |
| 5 | 线程启动规则 | start() hb 线程内所有动作 |
| 6 | 线程终止规则 | 线程内动作 hb join() 返回 |
| 7 | 线程中断规则 | interrupt() hb 检测到中断 |
| 8 | 对象终结规则 | 构造函数结束 hb finalize() |
2. volatile 内存屏障
volatile 写: [StoreStore] → 赋值 → [StoreLoad] (StoreLoad 最贵!)
volatile 读: 读取 → [LoadLoad] → [LoadStore]3. 锁升级路径
无锁 (01) ──→ 偏向锁 (01, biased=1) ──→ 轻量级锁 (00) ──→ 重量级锁 (10)
↑
hashCode() 调用阻止偏向锁4. MarkWord 锁状态位速查
| 状态 | biased | lock | 存储内容 |
|---|---|---|---|
| 无锁 | 0 | 01 | hashCode、GC age |
| 偏向锁 | 1 | 01 | 线程 ID、epoch、GC age |
| 轻量锁 | — | 00 | Lock Record 指针 |
| 重量锁 | — | 10 | ObjectMonitor 指针 |
| GC 标记 | — | 11 | GC 线程使用 |
五、面试高频追问一览
| 追问 | 关联笔记 | 频次 |
|---|---|---|
| JMM 是什么?为什么需要它? | **JMM与happens-before** | 极高 |
| happens-before 的 8 条规则? | **JMM与happens-before** | 极高 |
| happens-before 是时间先后关系吗? | **JMM与happens-before** | 高 |
| volatile 的两层语义? | **volatile的内存语义** | 极高 |
| volatile 能保证原子性吗? | **volatile的内存语义** | 极高 |
| DCL 为什么需要 volatile? | **volatile的内存语义** | 极高 |
| final 在 JMM 中有什么特殊语义? | **final的内存语义** | 高 |
| 构造函数中 this 逸出有什么问题? | **final的内存语义** | 高 |
| synchronized 的底层实现? | **synchronized的JVM实现** | 极高 |
| 锁升级的过程? | **锁升级过程** | 极高 |
| 偏向锁为什么被撤销? | **锁升级过程** | 高 |
| 轻量锁和重量锁的区别? | **synchronized的JVM实现** | 高 |
| 自适应自旋是什么? | **锁升级过程** | 中高 |
📚 相关链接
- **对象内存布局(MarkWord与Klass与实例数据)** — MarkWord 锁状态位与对象内存布局
- **方法内联和逃逸分析** — 逃逸分析为锁消除提供基础
- **锁消除与标量替换** — 锁消除依赖 JMM 规则和安全边界
- **Java堆** — 堆是共享变量存储的区域
- ← 返回 **JVM文库设计文档**