JAVA:简单易懂了解JMM
JVM 和 JMM 是 Java 中两个核心但又经常混淆的概念。简单来说:
JVM 是“运行时环境”,JMM 是“并发规则”。
下面我们从多个维度来详细对比:
一、核心定义对比
|
维度 |
JVM (Java Virtual Machine) |
JMM (Java Memory Model) |
|---|---|---|
|
本质 |
一个具体的软件/规范,是 Java 程序运行的平台和环境。 |
一组抽象的规则和规范,定义多线程下内存访问的行为。 |
|
类比 |
就像一个完整的工厂,有厂房、生产线、仓库、物流等实体设施。 |
就像工厂里的《安全生产手册》和《协同工作协议》,规定了工人之间如何传递物料、如何避免冲突。 |
|
目标 |
“一次编写,到处运行”,提供与平台无关的运行环境。 |
在多线程环境下,为程序员提供一致、可预测的内存可见性和有序性保证。 |
二、职责与功能对比
JVM 的职责(负责“做”)
-
类加载:将
.class文件加载到内存。 -
内存管理:分配和管理真实的物理内存区域,如:
-
堆:存放对象实例。
-
方法区:存放类信息、常量、静态变量。
-
虚拟机栈:存放局部变量、方法调用栈帧。
-
本地方法栈、程序计数器。
-
-
字节码执行:通过解释器或 JIT 编译器执行字节码。
-
垃圾回收:自动回收堆中不再使用的对象。
-
本地接口:与 Native 方法交互。
JMM 的职责(负责“管规则”)
-
定义线程与主内存的交互:规定变量如何从主内存复制到工作内存,以及如何同步回主内存。
-
提供内存可见性保证:确保一个线程的修改能被其他线程及时看到(通过
volatile、synchronized等规则)。 -
提供有序性保证:禁止特定类型的指令重排序(通过 Happens-Before 规则 和内存屏障)。
-
提供同步工具:定义了
synchronized、volatile、final等关键字在多线程环境下的语义。
关键区别:JVM 管理的堆、栈是真实的内存分区,是物理或逻辑上真实存在的。而 JMM 中的主内存、工作内存是抽象概念,前者主要对应堆,后者对应CPU缓存、寄存器、栈的一部分,但并非一一对应。
三、一个生动的类比
场景:一个大型图书馆(类比JVM)
-
JVM 是这个图书馆本身:
-
它有真实的书库(堆) 存放所有书籍(对象)。
-
它有阅览桌(栈) 供读者临时阅读。
-
它有图书管理员(GC) 定期清理没人看的书。
-
它有借还书系统(执行引擎) 处理读者的请求。
-
-
JMM 是图书馆的《借阅管理规定》:
-
规定多个读者(线程) 想同时看同一本书时怎么办。
-
规定一个读者在书上做了笔记(修改数据),如何让其他读者立刻看到(可见性)。
-
规定读者必须“先借书,后阅读”,这个顺序不能乱(有序性,防止重排序)。
-
规定一本书同一时间只能被一个读者借出做笔记(原子性,通过
synchronized实现互斥)。
-
没有 JMM:所有读者一窝蜂冲进书库抢书、乱写乱画,结果一片混乱,谁也不知道书的最新内容是什么。
有了 JMM:所有读者都按规则办事,图书馆秩序井然,信息传递高效准确。
四、JMM 的核心内容
JMM 主要围绕“线程如何与主内存交互”以及“线程间如何通信”来定义规则。它规定了以下三件事:
1. 原子性(Atomicity)
-
定义:一个或多个操作,要么全部执行且不会被打断,要么都不执行。
-
JMM 的保证:对基本类型(
long,double除外)的读取和赋值是原子性的。例如int a = 10;是原子的。 -
常见误区:
i++操作不是原子的,它涉及读取、计算、写入三个步骤。需要使用synchronized或AtomicInteger来保证其原子性。
2. 可见性(Visibility)
-
定义:一个线程修改了共享变量的值,其他线程能立即看到修改后的值。
-
实现机制:
-
volatile关键字:当写一个volatile变量时,JMM 会立即将该线程本地内存中的值刷新到主内存。当读一个volatile变量时,JMM 会使该线程的本地内存无效,强制从主内存重新读取。 -
synchronized关键字:在解锁前,必须将共享变量同步到主内存。在加锁时,会清空本地内存,从主内存重新加载。 -
final关键字:被final修饰的字段,在构造器初始化完成后,对其他线程是可见的(前提是对象引用没有“溢出”)。
-
3. 有序性(Ordering)
-
定义:程序执行的顺序按照代码的先后顺序执行。禁止指令重排序。
-
实现机制:
-
volatile关键字:通过插入内存屏障,禁止在volatile写之前的指令被重排序到写之后,也禁止在volatile读之后的指令被重排序到读之前。 -
synchronized关键字:一个变量在同一个时刻只允许一条线程对其进行lock操作,这使得持有同一个锁的两个同步块只能串行进入,从而保证了有序性。 -
Happens-Before 原则:这是 JMM 的灵魂和最核心的部分。
-
五、Happens-Before 原则
这是一组天然存在的、无需任何同步协助就能保证可见性和有序性的规则。如果操作 A “Happens-Before” 于操作 B,那么 A 所做的任何修改对 B 都是可见的,且 A 的执行顺序排在 B 之前。
-
程序次序规则:在同一个线程中,前面的操作 Happens-Before 于后面的任何操作。
-
监视器锁规则:一个
unlock操作 Happens-Before 于后续对同一个锁的lock操作。 -
volatile变量规则:对一个volatile变量的写操作 Happens-Before 于后续对这个变量的读操作。 -
传递性:如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
-
线程启动规则:
Thread.start()调用 Happens-Before 于这个线程内的任何操作。 -
线程终止规则:线程中的任何操作都 Happens-Before 于其他线程检测到该线程已经终止(如
Thread.join()成功返回,Thread.isAlive()返回 false)。 -
对象终结规则:一个对象的构造器结束 Happens-Before 于它的
finalize()方法开始。 -
线程中断规则:对线程
interrupt()的调用 Happens-Before 于被中断线程检测到中断事件。
注意:Happens-Before 并不一定意味着时间上的先后,它强调的是前一个操作的结果对后一个操作可见。
六、JMM 的关键抽象:主内存与工作内存
JMM 定义了以下抽象结构:
-
主内存:可以粗略地理解为 Java 堆,存储所有共享变量。
-
工作内存:每个线程私有的。可以粗略理解为 CPU 寄存器、缓存和栈的一部分。它存储了该线程使用到的变量的主内存副本。
线程、主内存、工作内存的交互关系:
-
线程对共享变量的所有操作(读、写)都必须在自己的工作内存中进行。
-
线程不能直接读写主内存中的变量,也不能直接访问其他线程的工作内存。
-
线程间变量的传递(通信)必须通过主内存来完成。
这个抽象模型是理解 volatile、synchronized等关键字如何工作的基础。
七、volatile关键字在 JMM 中的精确作用
1.保证可见性:如前所述,写操作立即刷新到主内存,读操作使本地内存失效。
2.禁止指令重排序:通过内存屏障实现。
3.不保证原子性:对 volatile变量的复合操作(如 i++)仍然不是原子的。
典型应用场景:
•状态标志位:private volatile boolean flag;
•一次性安全发布(结合 final使用):private volatile Singleton instance;(双重检查锁定模式)
JMM 的本质:是一套规范,它规定了 Java 编译器、JVM 运行时和处理器需要遵守什么样的内存访问规则,以在追求性能最大化的同时,为开发者提供一个清晰、一致的并发编程模型。当你理解了 JMM 和 Happens-Before 原则,你就能真正理解 synchronized、volatile、final等关键字的深层含义,从而编写出正确、高效的并发程序。
更多推荐


所有评论(0)