Java内存模型(JMM)与JVM内存模型
目录
Java 内存模型
JVM 内存模型
Java 内存模型
概述: Java 内存模型(JMM,Java Memory Model)是 Java 语言规范中定义的一种抽象内存模型,核心目的是解决多线程下通过共享内存通信时的三大问题,原子性、可见性、有序性。
JMM 核心目标:多线程环境下,线程对共享变量的读写可能因 CPU 缓存、指令重排等硬件/编译器优化导致的数据不一致问题,JMM 通过定义一套规则,让线程按照规则与共享内存中的变量进行交互,保证并发环境下程序的正确性。
抽象内存划分:JMM 将内存抽象为“主内存”与“工作内存”(是抽象概念,并非实际物理内存,可能是 CPU 缓存、寄存器等),主内存用于存储所有的共享变量,是线程共享的。工作内存是每个线程的私有内存空间,存储线程对共享变量的副本,线程操作共享变量时,需先将主内存变量加载到工作内存中,修改后再写回主内存。
可见性:确保一个线程修改共享变量后,能及时被其他线程看到,例如 volatile 关键字就能够保证可见性。
原子性:就是一个操作或者多个操作要么全部执行,要么都不执行,例如 Synchronized 关键字就能够实现原子性。
有序性:就是线程执行相关命令的顺序不能够被打乱,即禁止指令重排序。
JMM 中核心概念 Happens-Before 规则:改规则定义了两个操作之间的偏序关系,就是若 A Happens-Before B,则 A 的结果对 B 可见(JMM 核心)。例如其中的 volatile 变量规则:对 volatile 变量的写操作 Happens-Before 后续对这个 volatile 变量的读操作,从而通过限制特定指令重排,保证了可见性和有序性。
JVM 内存模型
JVM 内存模型又叫做 JVM 运行时数据区,主要划分为线程共享区域(堆、方法区)、线程私有区域(虚拟机栈、本地方法栈、程序计数器)这两大类。
堆:堆是 JVM 中最大的内存区域,用于存储对象实例和数组,是垃圾回收的主要区域,分年轻代(Eden 区、Survivor 区)和老年代。
方法区:存储类信息(结构、方法、字段)、常量、静态变量等,JDK8 后用元空间(Metaspace)实现,取代永久代,物理上属于本地内存。
虚拟机栈:存储方法调用的“栈帧”(局部变量、操作数栈等),方法调用结束后栈帧出栈。
本地方法栈:即 Native 方法(如 C 语言实现的本地方法),提供栈空间,主要为虚拟机使用 Native 方法提供服务。
程序计数器:就是当前线程所执行的字节码的行号计数器,用于记录当前线程正在执行的字节码指令地址,是 JVM 执行引擎的控制指针。
运行时常量池:是方法区的一部分,存储编译期生成的各种字面量和符号引用,……。
结尾
读懂JMM,才算懂了Java并发的“底层逻辑”。
回头看我们聊的Happens-Before规则、volatile的内存屏障,其实都在围绕一个核心——JMM从不是“一刀切禁止指令重排”,而是在“性能优化”和“并发安全”之间找平衡:它允许编译器、CPU做合理的重排来提效,却用规则圈定了“不能乱排”的边界,确保多线程下共享变量的可见性、有序性不被破坏。
很多时候,我们写并发代码时遇到的诡异bug(比如线程读不到最新值、执行顺序错乱),根源往往不是逻辑错了,而是没吃透JMM的“约束逻辑”——比如误以为volatile能保证原子性,或是忽略了Happens-Before的隐式规则。毕竟JVM内存模型是“where”(数据存哪),而JMM才是“how”(多线程怎么安全交互数据),搞懂这两者的分工,才算真正摸透了Java并发的底层骨架。
如果你在项目里踩过并发的坑,或是对某个规则有更具体的疑问,欢迎在评论区分享你的经历——咱们一起从“知其然”走到“知其所以然”,把JMM的逻辑变成写并发代码的“安全感”。
感谢读到这里,技术之路漫长,同行者常伴,期待与你下次交流,感兴趣的话不妨点个关注~~
