讲一下JVM的内存模型
JVM内存模型(Java Memory Model,JMM)是Java并发编程的核心基础,也是面试中的高频考点。它主要解决了多线程环境下共享数据的可见性、原子性和有序性问题,定义了线程和主内存之间的抽象关系。
一、JMM的核心目标
- 解决多线程内存可见性问题:多个线程如何看到彼此的内存修改
- 规范指令重排序规则:在不影响单线程执行结果的前提下,允许编译器和CPU对指令重排序,但需保证多线程安全
- 定义happens-before规则:建立操作之间的偏序关系,确保多线程环境下的执行结果可预测
二、JMM的内存抽象模型
JMM将内存划分为两种:
- 主内存:所有线程共享的内存区域,存储共享变量(实例字段、静态字段等)
- 工作内存:每个线程独有的内存区域,存储共享变量的副本
交互规则:
- 线程对共享变量的操作必须在自己的工作内存中进行,不能直接读写主内存
- 线程修改后的变量需同步回主内存,其他线程才能看到最新值
三、三大特性及保证机制
1. 原子性(Atomicity)
- 定义:一个操作或多个操作要么全部执行且执行过程不被中断,要么都不执行
- 保证方式:
- 基本数据类型的读取和赋值操作是原子性的(long和double除外,在32位系统可能非原子)
synchronized关键字:通过同步锁保证代码块的原子性java.util.concurrent.atomic包:提供CAS操作实现原子性(如AtomicInteger)
2. 可见性(Visibility)
- 定义:当一个线程修改了共享变量的值,其他线程能立即看到修改后的值
- 保证方式:
volatile关键字:强制变量修改后立即同步到主内存,读取时直接从主内存加载synchronized:解锁前必须将变量同步回主内存final:被final修饰的变量初始化后不可修改,具有可见性
3. 有序性(Ordering)
- 定义:程序执行的顺序按照代码的先后顺序执行
- 问题来源:
- 编译器优化重排序
- CPU指令重排序
- 内存系统重排序
- 保证方式:
volatile:禁止指令重排序(通过内存屏障)synchronized:保证同一时刻只有一个线程执行同步块,相当于顺序执行- happens-before规则:无需任何同步手段即可保证有序性的天然规则
四、Happens-Before规则(高频考点)
JMM定义的天然有序性规则,无需额外同步操作:
- 程序顺序规则:单线程中,前面的操作happens-before后面的操作
- volatile变量规则:对volatile变量的写操作happens-before后续的读操作
- 锁规则:解锁操作happens-before后续的加锁操作
- 传递性:若A happens-before B,B happens-before C,则A happens-before C
- 线程启动规则:
Thread.start()happens-before线程中的任何操作 - 线程终止规则:线程中的所有操作happens-before其他线程检测到该线程终止
- 线程中断规则:对线程的中断操作happens-before被中断线程感知到中断
- 对象终结规则:对象的构造函数执行完成happens-before其finalize()方法
五、Volatile关键字的作用(重点)
- 保证可见性:变量修改后立即同步到主内存
- 禁止指令重排序:通过插入内存屏障实现:
- 写操作后插入StoreStore屏障,防止后续写操作重排序到前面
- 写操作后插入StoreLoad屏障,防止读操作重排序到写操作前面
- 读操作前插入LoadLoad屏障,防止前面的读操作重排序到后面
- 读操作后插入LoadStore屏障,防止后面的写操作重排序到前面
- 不保证原子性:如
i++操作仍需同步手段保证原子性
六、常见面试题解析
Q:JMM和JVM内存结构的区别?
A:JMM是抽象模型,关注多线程内存交互;JVM内存结构是具体划分(如堆、方法区、虚拟机栈等),关注内存分配。Q:Volatile和Synchronized的区别?
A:Volatile轻量级,保证可见性和有序性,不保证原子性;Synchronized重量级,保证三大特性,可修饰方法和代码块。Q:为什么DCL单例模式需要Volatile?
A:防止指令重排序导致的"半初始化"问题,instance = new Singleton()可能被拆分为分配内存、初始化、赋值三步,重排序后可能导致其他线程获取到未初始化的实例。
JMM是理解Java并发编程的基础,掌握其核心概念(三大特性、happens-before规则、volatile作用)对解决并发问题和应对面试至关重要。