引言
本文主要讲述了JVM内存模型(运行时数据区)和JMM(Java Memory Model)的核心区别及联系,并附上各自的概念图,希望能对读者的理解有所助益
创作时间:2024-10-01
更新时间:2024-12-31
资源:
一、JVM内存模型(运行时数据区)
定义:JVM内存模型描述的是JVM物理内存的划分,即Java程序运行时的内存区域

解释说明:
| 内存区域 | 作用 | 线程隔离性 | 溢出错误 | 备注 |
|---|---|---|---|---|
| 程序计数器 | 记录当前线程执行到的字节码位置 | 线程私有 | 无溢出 | |
| 本地方法栈 | 支持Native方法(如C/C++代码等) | 线程私有 | StackOverflowError | |
| 虚拟机栈 | 存储方法调用的栈帧(局部变量、操作数栈等) | 线程私有 | StackOverflowError | 虚拟机栈的每个栈帧对应一个方法调用 |
| 堆 | 存储对象实例和数组(发生GC的地方) | 线程共享 | OutOfMemoryError | 堆是对象分配的主要区域,是GC的主要区域(垃圾回收仅管理堆内存中的对象,栈内存中的变量和引用随线程结束自动释放,不依赖GC处理) |
| 元空间 | 存储类型信息、常量、静态变量 | 线程共享 | OutOfMemoryError | JDK 8以前称为方法区,使用的本地内存 ;元空间会触发特定垃圾回收,称为类卸载(Class Unloading) |
二、JMM(Java Memory Model)
定义:JMM是多线程环境下共享数据访问的规范,其屏蔽了各种硬件和操作系统的访问差异,解决了线程间的原子性,可见性和有序性问题

解释说明:
| 概念 | 用途/释义 | 备注 |
|---|---|---|
| JMM三大特性 | 原子性、可见性、有序性 | |
| 主内存与工作内存 | 工作内存是每个线程私有内存,而共享变量存储在主内存中 | |
| 内存交互操作 | read/load:读取到工作内存;use/assign:使用和赋值;store/write:写回主内存 | 详见下文 |
| happens-before | 定义操作之间的顺序规则(如锁释放先于获取,volatile写先于读等) | |
| volatile | 保证变量的可见性(强制写改变刷新到主内存)和顺序性(禁止指令重排序),但不保证原子性 | |
| synchronized | 通过锁机制保证原子性、可见性和顺序性 |
| 内存交互操作 | 作用 | 备注 |
|---|---|---|
| read(读取) | 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用 | |
| load(载入) | 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中 | |
| use(使用) | 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作 | |
| assign(赋值) | 作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作 | |
| store(存储) | 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用 | |
| write(写入) | 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中 | |
| lock(锁定) | 作用于主内存的变量,它把一个变量标识为一条线程独占的状态 | |
| unlock(解锁) | 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 |
三、总结
3.1 核心区别
| 维度 | JVM内存模型 | JMM内存模型 | 备注 |
|---|---|---|---|
| 定位 | 描述物理内存如何划分结构(运行时数据区) | 逻辑上定义多线程环境下共享数据访问的规范 | |
| 关注点 | 内存分配、垃圾回收、方法调用栈 | 线程间通信及数据的同步 | |
| 线程共享性 | 堆和元空间是共享的,栈是私有的 | 主内存是共享的,工作内存是私有的 | |
| 典型问题 | 内存溢出(OOM)、栈溢出(SOF) | 竞态条件、内存可见性 | |
| 解决方案 | 调整堆参数(-Xmx)、优化代码逻辑 | volatile、synchronized、lock等 |
3.2 联系
- JVM内存模型具象定义“内存怎么用”,JMM定义规范“多线程怎么安全地用内存”
- 底层实现依赖:JMM的规则(如volatile和synchronized)需要JVM在内存操作层面支持
- 协同工作:
- JVM的堆实际存储共享对象,JMM定义规范确保多线程正确访问这些对象
- JVM的栈实际存储方法调用,JMM定义规范解决栈中局部变量在多线程环境下的可见性问题(如果变量被共享)
解释说明:
- 竞态条件(Race Condition):指多个线程或进程在缺乏同步机制的情况下,无序访问共享资源时,程序的最终结果依赖于无法预测的执行顺序,导致数据错误或行为异常的现象。例如,两个线程同时修改同一变量时,最终值可能仅反映其中一个线程的操作,而另一线程的修改被覆盖。
- as-if-serial语义:不管怎么指令重排序(为了提高并行度),程序的执行结果不能被改变。该原则要求编译器、运行时和处理器在优化时必须遵守数据依赖性约束,即仅对不存在数据依赖关系的操作进行重排序
- 原子性(Atomicity):一个操作是不可分割的、不可中断的;JMM直接保证原子性的有read、load、assign、use、store、write、lock和unlock,其中尽管虚拟机未把lock和unlock操作直接开放给用户使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作;这两个字节码指令反映到Java代码中就是同步块synchronized关键字,或者其它同步锁
- 可见性(Visibility):一个线程修改了共享变量的值时,其它线程需要能立即感知到,若不能立即知道,则称为无法保证可见性;Java提供了volatile和synchronized两个关键字来保证可见性:
- volatile是通过汇编lock指令强制将写改变刷新到主内存来实现的
- synchronized修饰或者加锁的同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操 作)”这条规则获得的
- 有序性(Ordering):在多线程环境下,由于执行语句重排序后,可能会出现顺序错乱的问题(和实际指令书写顺序不符);Java提供了volatile和synchronized两个关键字来保证有序性:
- volatile关键字本身就包含了禁止指令重排序的语义
- synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入
- 方法调用栈(Method Call Stack):又称执行栈、控制栈或运行时栈,是用于存储程序运行时子程序调用信息的栈结构,其核心功能包括保存子程序返回地址、维护执行上下文及处理嵌套调用,在高级语言中通常由系统自动管理
- 调用栈通过入栈和出栈操作记录函数调用顺序,递归调用时逐层保存返回地址,超出容量会导致栈溢出;在汇编语言等底层编程中需手动保存返回地址以防止寄存器覆盖,此时调用栈同时存储控制流信息和数据,可能因栈缓冲区溢出引发安全漏洞
- 调用栈分析在软件安全领域具有应用价值,例如通过跟踪反序列化漏洞触发时的调用栈状态,可定位特定类解析过程中的异常行为。XStream反序列化等操作涉及调用栈动态变化,成为漏洞检测的关键观察对象
TODO:
- 从方法区(PermGen)到元空间(Metaspace)
- long和double的非原子性协定,32位JVM下Long类型数据读取不是原子性的
参考资料
- https://blog.youkuaiyun.com/Triste__chengxi/article/details/149020987
- https://www.jianshu.com/p/a6f19189ec62
- https://cloud.tencent.com/developer/article/2527660
- https://cloud.tencent.com/developer/article/2463605
- https://www.kancloud.cn/luoyoub/jvm-note/1890100
- https://blog.youkuaiyun.com/nandao158/article/details/139268406
免责申明:相关文章及资料仅供学习交流使用,禁止一切不正当行为,如由此产生相关责任,自行承担
Tip:如需转发或引用相关内容,请务必附带原链接
如果对你有帮助的话,麻烦关注一波,并且点赞、收藏、转发一下哦o( ̄︶ ̄)o!如果有问题或者发现Bug欢迎提出反馈!
1518

被折叠的 条评论
为什么被折叠?



