如果你在搜索引擎里输入“java内存模型”,看到的结果很可能分成两拨:一拨是Java虚拟机运行时数据区,讲堆、栈、方法区;另一拨是主内存与工作内存、volatile、happens-before、内存屏障。两拨内容都挂着“java内存模型”的名头,但本质不是一回事。我自己学JMM的第一感受就是:必须先做概念清障,否则后面全是浆糊。
这篇内容是我的一篇学习记录,适合正在面试前突击并发基础的同学,也适合写多线程代码总是“看着没问题、跑起来随机出错”的人。我会把容易混淆的概念掰开,再结合几个经典案例把规则落到代码里,最后整理一份常见问题速查表。目标是让你看完以后,能用自己的话把“java内存模型到底是什么”讲清楚,而不是被八股文牵着鼻子走。
1. 一次概念清障:JMM不是JVM内存布局
1.1 我最初的理解错在哪
最开始我把“java内存模型”等同于“JVM内存结构”,后来发现完全不是。JVM内存结构指的是虚拟机运行时把内存分成程序计数器、虚拟机栈、本地方法栈、堆、方法区(Java 8之后多数实现用元空间代替永久代)。这是和“对象放哪、方法栈帧在哪”相关的话题。而JMM关心的是:在多线程环境下,一个线程对共享变量的写入,什么时候对另一个线程可见;编译器、CPU为了性能做的指令重排,边界在哪里。
换句话说,JVM内存结构管的是“数据怎么在内存区域里放”,JMM管的是“多线程访问共享数据时,你能看到什么、看不到什么”。一个偏运行时布局,一个偏并发语义。
1.2 JMM到底在解决什么问题
Java内存模型是一套规范,不是某个实际存在的物理内存区域。它描述的是Java程序中的变量(成员变量、静态变量、数组元素,但不包括局部变量和方法参数)在不同线程之间的一致性规则。
为什么需要这套规则?因为现代CPU有缓存和指令重排。同一个变量可能被多个CPU核读取,如果一个线程修改了变量但还没写回主内存,另一个线程读到的就是旧值。指令重排又会让代码的执行顺序“看起来”变了,尤其是写操作和读操作交替出现时,问题更容易暴露。
我常用一个类比:公司里有一个公共公告栏(主内存),每个员工手里还有一块私人白板(工作内存/CPU缓存)。员工看通知先看自己的白板,白板没有才去公告栏拿最新版。问题是,员工在白板上改了通知内容,可能并不会立刻贴回公告栏。另一个员工过来看,看到的是白板上的旧通知。JMM就是规定“什么时候必须同步公告栏”“什么时候不能只改白板”的规则集。
1.3 别把“JMM”和“JVM内存结构”混在一起背
很多搜索热词里“jvm内存模型”“java8jvm内存模型中是否还有方法区”指向的是同一个困惑。如果你准备面试,这两块都要知道,但不要混在一起背。
JVM运行时数据区是分区域的,比如堆是共享的,栈是线程私有的。JMM则把共享内存抽象成主内存,把每个线程的操作抽象成工作内存。本质上,JMM的这个抽象是用来对应CPU缓存、寄存器这些东西的,不是在描述JVM自己怎么分配内存。
顺带说一句Java 8的变化:规范里的“方法区”仍然存在,它只是逻辑上的概念,落地实现从永久代换成了元空间。更准确地说,Java 8移除的是永久代,不是方法区。元空间使用本地内存,不再容易触发永久代溢出,这是另一个话题了。但如果你听到“Java 8没有方法区”这种说法,至少要知道它省略了“实现方式变了”这个前提。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. JMM的核心:可见性、有序性、原子性
2.1 三个性质分别是什么意思
并发Bug的根源可以收敛到三个词:可见性、有序性、原子性。
可见性是指一个线程修改共享变量后,其他线程能不能立即看到这个修改。造成不可见的原因是CPU缓存和指令重排。有序性是指程序执行结果不能因为指令重排而变乱。原子性是指一组操作要么全部执行成功,要么全部不执行。
JMM对原子性其实没有提供太多的直接保证,它更聚焦在可见性和有序性上。原子性通常交给synchronized、Lock、Atomic类去解决。刚开始学的时候,很容易把“volatile可以保证可见性”误解成“volatile可以保证原子性”,这是最大的坑之一。
举个例子,两个线程同时执行 count++。count++ 并不是一条原子操作,它至少包含“读取 count、
