前面讲的是 JVM 运行时的内存区域划分。而 JVM 还有一个重要的内存模型的概念,名字有点像,但其实讲的是并发环境下,JVM 内存中的共享变量的访问规范。它叫 JMM,Java 内存模型。

该模型仅针对并发环境下,多个线程之间共享变量的场景。例如 class 的成员变量,在多线程环境下,不同的线程改变该成员变量的值时,如何在线程之间控制和通信。JMM 本质上是一个缓存一致性协议。它的目的,是为了在多 CPU 核环境下,在尽量利用硬件提高计算性能的同时,保证缓存一致性。

硬件的效率与一致性

现代计算机执行计算任务时总是尽量让多个 cpu 尽量并行计算,但计算任务并非只有 cpu 就行,它总是需要读写内存数据;但内存 IO 的速度和 CPU 计算的速度之间有几个数量级的差距,因此现代 CPU 设计了多层高速缓存,让数据尽量离 CPU 更近一点。但高速缓存引入了一个新的问题,那就是缓存一致性。多个 CPU 分别使用自己的高速缓存进行读写后,需要将数据写回内存,如果各自不一致怎么办?以谁为准?为了解决这个问题,就需要在高速缓存和内存之间使用统一的规则来进行数据同步,这就是缓存一致性协议。

Java 内存模型

JMM 本身的设计如下:

JMM 规范将共享变量所在内存划分为 主内存工作内存。主内存为各线程共享,工作内存为各线程私有。当线程操作共享变量时,它需要将共享变量从主内存复制一份到工作内存中,在工作内存中修改之后再写回主内存。线程只能直接操作工作内存中的变量副本,变量副本与主存之间的读取和写入都是由实现了 JMM 规范的某种机制实现。

共享变量包括成员字段、静态字段以及数组中的元素。如果共享变量是一个基本数据类型,工作内存中的副本也是基本数据类型;如果共享变量是一个对象,工作内存中的副本就是该对象的引用。

JMM 提供了 4 种操作来完成主存和工作内存之间的变量同步机制,分别是 readwritelockunlock。这里不作细述。

一开始是 8 种,后来出于降低理解难度和严谨性的考虑,降为 4 种。利用这四种操作,JMM 能够实现多个线程间对共享变量操作的原子性,可见性和有序性。

JMM 与 JVM 运行时内存区域

JMM 和 JVM 运行时内存区域其实没啥关系,但主存和工作内存的划分,容易和 JVM 运行时的各种内存区域产生联想,导致概念上的混淆。

周志明先生的《深入理解 Java 虚拟机》一书中有下面的叙述:

如果两者一定要勉强对应起来, 那么从变量、主内存、工作内存的定义来看, 主内存主要对应于 Java 堆中的对象实例数据部分, 而工作内存则对应于虚拟机栈中的部分区域。从更基础的层次上说, 主内存直接对应于物理硬件的内存, 而为了获取更好的运行速度, 虚拟机(或者是硬件、操作系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中, 因为程序运行时主要访问的是工作内存。

另外可以参考下面这篇文章来理解二者之间对应关系:

Java Memory Model

大致意思就是主存主要对应堆,而工作内存属于虚拟机栈的一部分。但二者并非完全对应。JVM 运行时内存区域并不是只对应硬件上的 RAM 内存,它的堆和栈都有可能部分分配在 RAM 上,部分分配在 CPU 寄存器与 CPU 高速缓存上。只是一般而言,堆区大部分位于 RAM 上,而虚拟机栈则有部分会分配在高速缓存上。至于工作内存,则应该随着虚拟机栈一起优先分配于寄存器和高速缓存中。