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

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


一、JMM 的核心概念

1. 什么是 JMM?

  • Java 内存模型(JMM) 是 Java 虚拟机(JVM)定义的一套规范,用于解决多线程环境下的内存可见性、原子性和有序性问题。
  • 它屏蔽了不同硬件架构(如 x86、ARM)的内存访问差异,确保 Java 程序在不同平台上表现一致。

2. JMM 的作用

  • 保证多线程安全:防止线程间数据竞争和内存不一致问题。
  • 提供原子性、可见性和有序性
    • 原子性:操作不可分割(如 i++ 不是原子操作)。
    • 可见性:一个线程修改变量后,其他线程能立即看到最新值。
    • 有序性:程序执行的顺序符合代码逻辑(避免指令重排序)。

二、JMM 的核心机制

1. 主内存(Main Memory)与工作内存(Working Memory)

  • 主内存:所有线程共享的内存区域,存储 Java 变量的实际值(对应 JVM 堆中的对象实例数据)。
  • 工作内存:每个线程私有的内存区域,存储主内存中变量的副本(类似 CPU 的寄存器或缓存)。
    • 线程操作变量
      1. 从主内存读取变量到工作内存。
      2. 在工作内存中修改变量。
      3. 将修改后的值写回主内存。

2. 内存间的交互操作(8 种原子指令)

JMM 定义了线程与主内存之间的交互操作,确保数据同步:

操作作用
read从主内存读取变量到工作内存(仅读取,不修改)。
loadread 读取的值放入工作内存的变量副本。
use将工作内存的变量值传递给执行引擎(如方法调用)。
assign将执行引擎的值赋给工作内存的变量(如 i = 10)。
store将工作内存的变量值写入主内存(仅写入,不读取)。
writestore 写入的值更新到主内存的变量。
lock锁定主内存的变量(独占访问)。
unlock解锁主内存的变量(释放独占访问)。

3. 三大特性

(1)原子性(Atomicity)
  • 定义:一个操作或一组操作要么全部执行完成,要么完全不执行。
  • JMM 保证的原子性
    • 基本数据类型的读写(如 intlong,但 longdouble 在 32 位 JVM 上可能非原子)。
    • volatile 变量的读写(仅保证单个变量的原子性)。
    • synchronized 块内的代码(复合操作的原子性)。
  • 非原子操作示例
    i++;  // 实际是 read -> load -> use -> assign -> store -> write 的组合操作
    
(2)可见性(Visibility)
  • 定义:一个线程修改共享变量后,其他线程能立即看到最新值。
  • JMM 如何保证可见性
    • volatile 关键字:强制刷新工作内存到主内存,并禁止指令重排序。
    • synchronized:进入同步块时清空工作内存,退出时刷新主内存。
    • final:正确构造的对象引用对其他线程立即可见。
(3)有序性(Ordering)
  • 定义:程序执行的顺序符合代码逻辑(避免指令重排序)。
  • JMM 如何保证有序性
    • 单线程内有序:编译器和处理器会优化代码顺序(as-if-serial 语义),但结果不变。
    • 多线程间有序
      • volatile:禁止指令重排序(通过插入内存屏障)。
      • synchronized:同步块内的代码按顺序执行。
      • `happens-before 规则**(见下文)。

三、happens-before 规则

JMM 通过 happens-before 规则 定义多线程操作的先后顺序,确保程序的正确性。常见规则:

  1. 程序次序规则:单线程内代码按书写顺序执行(as-if-serial)。
  2. 监视器锁规则unlock 操作 happens-before 后续的 lock 操作。
  3. volatile 变量规则volatile 写操作 happens-before 后续的 volatile 读操作。
  4. 传递性:A happens-before B,B happens-before C ⇒ A happens-before C。
  5. 线程启动规则Thread.start() happens-before 子线程的任何操作。
  6. 线程终止规则:子线程的所有操作 happens-before Thread.join() 的返回。

四、JMM 与并发编程

1. 常见并发问题

问题原因解决方案
可见性问题线程修改变量后未刷新到主内存,其他线程读到旧值。使用 volatilesynchronized
原子性问题复合操作(如 i++)被拆分为多个步骤,导致竞争。使用 synchronizedAtomicInteger
有序性问题编译器/处理器重排序导致逻辑错误(如双重检查锁问题)。使用 volatilefinal

2. 关键关键字

关键字作用适用场景
volatile保证可见性和禁止指令重排序(不保证复合操作的原子性)。单次读写、状态标志(如 boolean flag)。
synchronized保证原子性、可见性和有序性(互斥锁)。复合操作(如 i++)、临界区保护。
final保证对象构造完成后对其他线程可见(需正确初始化)。不可变对象、线程安全设计。

五、JMM 与硬件内存模型

1. 硬件内存模型(如 x86、ARM)

  • 现代 CPU 有多级缓存(L1/L2/L3),可能导致:
    • 可见性问题:线程 A 修改缓存中的数据,线程 B 可能读到旧值。
    • 有序性问题:CPU 可能重排序指令以提高性能(通过内存屏障禁止重排序)。

2. JMM 如何适配硬件

  • JMM 通过**内存屏障(Memory Barrier)**屏蔽硬件差异:
    • 写屏障(Store Barrier):确保数据写入主内存。
    • 读屏障(Load Barrier):确保从主内存读取最新数据。
    • 插入位置:在 volatilesynchronized 等关键操作前后插入屏障。

六、总结

核心概念说明
主内存与工作内存线程通过工作内存操作变量,最终同步到主内存。
三大特性原子性、可见性、有序性(通过 volatilesynchronized 等保证)。
happens-before定义多线程操作的先后顺序,避免数据竞争。
硬件适配通过内存屏障解决 CPU 缓存和指令重排序问题。

关键实践

  1. 多线程共享变量优先使用 volatilesynchronized
  2. 避免直接依赖 final 的可见性(需正确构造对象)。
  3. 使用并发工具类(如 AtomicIntegerConcurrentHashMap)替代手动同步。

示例代码

// 正确使用 volatile 保证可见性
class Counter {
    private volatile int count = 0;

    public void increment() {
        count++;  // 仍非原子操作(需用 AtomicInteger 或 synchronized)
    }

    public int getCount() {
        return count;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值