Java 内存模型(Java Memory Model, JMM)解析

Java 内存模型(Java Memory Model, JMM)深度解析

"Write once, run anywhere" 不仅靠字节码,更靠清晰定义的 Java 内存模型

它回答了:在多线程环境下,一个线程对共享变量的写入何时对另一个线程可见?


目录

  1. 为什么需要 JMM
  2. 关键抽象:主内存 & 工作内存
  3. Happens-Before 规则(HB 规则)
  4. 内存屏障与指令重排
  5. volatile、synchronized、final 的 JMM 语义
  6. DCL 问题与 volatile 的救场
  7. Java 9+ 的 JMM 演进(VarHandle、Opaque)
  8. 常见误区与调优建议
  9. 一张图速记

1. 为什么需要 JMM

现象 根源 JMM 作用
缓存不一致 CPU 多级缓存 规定可见性协议
指令重排 / 编译器重排 性能优化 用 HB 规则禁止危险重排
原子性无法保证 非原子读写(long/double) 提供锁或 CAS 语义

没有 JMM,所有并发行为都 "由 JVM 实现细节决定",程序不可移植。


2. 关键抽象:主内存 & 工作内存

text 复制代码
           ┌─────────────┐
           │  主内存      │  所有线程共享的"唯一真相"
           └──────┬──────┘
                  │
    ┌─────────────┴─────────────┐
    │                           │
┌───▼───┐    ┌───▼───┐    ┌───▼───┐
│工作内存A│    │工作内存B│    │工作内存C│ 每个线程私有
└───┬───┘    └───┬───┘    └───┬───┘
    │            │            │
  CPU0          CPU1         CPU2
  • 工作内存:线程私有的高速缓存/寄存器副本。
  • 主内存:真正的堆上对象字段。
  • 线程对变量的所有操作都必须先复制到工作内存,再刷回主内存。
  • 带来 可见性问题 :A 线程写入 x=1,B 线程看到的仍是 x=0

3. Happens-Before 规则(JSR-133 核心)

HB 关系是 偏序 ;若 A hb B,则 A 的写对 B 可见,且禁止编译器把 B 重排到 A 之前。

  1. 程序次序规则

    单线程内,代码书写顺序 happens-before 其后的操作。

  2. 锁规则

    解锁 hb 随后的加锁。

  3. volatile 规则
    volatilehb 任意后续 volatile 读。

  4. 线程启动规则
    Thread.start() hb 该线程的所有动作。

  5. 线程终止规则

    线程所有动作 hb 任何检测到该线程结束的代码(t.join() 返回)。

  6. 中断规则

    对线程 interrupt() hb 检测到中断抛出 InterruptedException

  7. 终结器规则

    构造函数完成 hb finalize() 开始。

  8. 传递性
    A hb BB hb CA hb C


4. 内存屏障与指令重排

JMM 在底层映射为 CPU 内存屏障(Fence)

屏障类型 作用
LoadLoad 禁止读读重排
StoreStore 禁止写写重排
LoadStore 禁止读写重排
StoreLoad 全能型屏障,成本最高(x86 唯一)

HotSpot 在 volatile 写后插 StoreLoad,在读前插 LoadLoad+LoadStore


5. 关键字语义一览

特性 可见性 原子性 可重排序 典型场景
volatile 单次读/写 状态标志、DCL 单例
synchronized 代码块 临界区、复合操作
final ✅(构造函数完成后) 初始化 不可变对象、安全发布

5.1 volatile 深度

java 复制代码
class Counter {
    volatile int count = 0;
    void inc() { count++; }   // 仅保证可见,不保证原子!
}
  • 读/写操作本身会插入屏障;复合操作需配合 AtomicInteger

5.2 synchronized 的 JMM 视角

java 复制代码
synchronized(lock) {
    // 进入:Load屏障,使工作内存失效
    // 离开:Store屏障,把修改强刷回主内存
}
  • 解锁时强制刷回,加锁时强制失效,天然 互斥+可见

5.3 final 安全发布

java 复制代码
class Immutable {
    final int x;
    public Immutable(int x) { this.x = x; } // 写 final
}
// 正确发布
Immutable ref = new Immutable(42);  // 写 ref
  • 写 final 与写 ref 禁止重排 ;其他线程读 ref.x 一定看到 42。

6. Double-Checked Locking 问题 & 解法

java 复制代码
// 有问题的 DCL
class Singleton {
    private static Singleton instance;
    public static Singleton get() {
        if (instance == null) {          // 第一次检查
            synchronized(Singleton.class) {
                if (instance == null)    // 第二次检查
                    instance = new Singleton(); // 可能重排到写引用之前
            }
        }
        return instance;
    }
}

问题:写对象与写引用可能被重排,导致返回 半初始化对象

解法:把 instance 声明为 volatile,禁用重排。


7. Java 9+ 的演进

  • VarHandle :提供 CAS、内存屏障、原子数组操作,绕过 sun.misc.Unsafe
  • Opaque 模式:仅禁止编译器重排,不做 CPU 屏障,用于极致性能场景。
  • JEP 188:标准化多线程语义测试(jcstress)。

8. 常见误区 & 调优建议

误区/做法 正确理解
使用 volatile 保证复合操作原子 Atomic* 或锁
认为 synchronized 一定慢 偏向锁/轻量级锁常接近无锁
HashMapConcurrentHashMap 就安全 只保证结构安全,复合逻辑仍需同步
滥用 System.gc() 解决内存可见 与 JMM 无关,甚至会引入额外 STW

建议

  1. 尽量用 java.util.concurrent 高级工具 ,不要裸写 wait/notify
  2. 共享可变变量统一用 volatile 或锁;不可变对象最安全。
  3. 压测时打开 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 查看屏障指令。

9. 一张图速记

graph TD A[Java Code] --> B(编译器/CPU 重排?) B -->|禁止| C{Happens-Before} C --> D((volatile write
synchronized unlock
Thread.start...)) D --> E(内存屏障
LoadStore/StoreLoad...) E --> F(可见性保证)

结语

JMM 不是 JVM 的实现细节,而是给所有并发程序员的"契约"

写出正确高效的并发代码,关键是让代码满足 HB 关系,而非迷信特定硬件行为。

相关推荐
尘鹄2 小时前
go 初始化组件最佳实践
后端·设计模式·golang
墩墩分墩2 小时前
【Go语言入门教程】 Go语言的起源与技术特点:从诞生到现代编程利器(一)
开发语言·后端·golang·go
程序员爱钓鱼5 小时前
Go语言实战案例- 开发一个ToDo命令行工具
后端·google·go
学渣676565 小时前
文件传输工具rsync|rust开发环境安装|Ascend实验相关命令
开发语言·后端·rust
我是渣哥5 小时前
Java String vs StringBuilder vs StringBuffer:一个性能优化的探险故事
java·开发语言·jvm·后端·算法·职场和发展·性能优化
晚安里6 小时前
JVM相关 4|JVM调优与常见参数(如 -Xms、-Xmx、-XX:+PrintGCDetails) 的必会知识点汇总
java·开发语言·jvm·后端·算法
齐 飞8 小时前
SpringBoot实现国际化(多语言)配置
java·spring boot·后端
David爱编程9 小时前
锁升级机制全解析:偏向锁、轻量级锁、重量级锁的秘密
java·后端
技术小泽9 小时前
深度解析Netty架构工作原理
java·后端·性能优化·架构·系统架构
摸鱼仙人~11 小时前
Spring Boot 拦截器(Interceptor)与过滤器(Filter)有什么区别?
java·spring boot·后端