JVM和JMM内存模型的区别

JVM内存模型

java虚拟机,可以让class文件在不同操作系统上运行。
1、:主要存储我们的创建的对象
2、方法区:存储类信息、常量、静态变量等
3、:jvm会在栈上给每个线程分配独立的内存空间,来存储线程的局部变量,每个方法执行时,会给其分配一个栈帧。
栈主要包含
3.1、局部变量表:存储局部变量
3.2、操作数栈:程序运行过程中,要做操作的临时中转存储空间
3.3、动态链接:存储方法在方法区中的地址(程序运行时需要将符号引用转为直接引用)
程序执行顺序:栈的结构与数据结构栈一致,是FILO;先执行main方法,则会将main方法的栈帧先入栈,再执行test方法入栈;当test方法执行完后,test对应的栈帧要出栈,即释放该栈帧空间

public class Demo{
    public int test() {  //一个方法对应一块栈帧内存区域
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

    public static void main(String[] args) {
        Demo demo = new Demo ();
        demo.test();
    }
}

分析下test方法的字节码文件

public int test();
Code:
0: iconst_1
# 将int类型常量1(数值1)压入操作数栈
1: istore_1
# 将int类型值存入局部变量1 ,将数值1从操作数栈取出,存入局部变量a,即a=1
2: iconst_2
# 将int类型常量2(数值2)压入操作数栈
3: istore_2
# 将int类型值存入局部变量2 ,将数值2从操作数栈取出,存入局部变量b,即b=2
4: iload_1
# 从局部变量1中装载int类型值,从局部变量表中取出a的值,压入操作数栈
5: iload_2
# 从局部变量2中装载int类型值,从局部变量表中取出b的值,压入操作数栈
6: iadd
# 从操作数栈中取出a、b的值,在cpu寄存器上执行运算得到3,然后将3再次压入操作数栈
7: bipush 10
# 将一个8位带符号整数压入栈,将10压入操作数栈
9: imul
# 执行int类型的乘法,将操作数栈上的3和10取出,在cpu寄存器上执行运算得到30,
将30再次压入操作数栈
10: istore_3
# 将int类型值存入局部变量3,将数值30从操作数栈取出,存入局部变量表,即c=30
11: iload_3
# 从局部变量3中装载int类型值,从局部变量表中取出c的值,压入操作数栈
12: ireturn
# 从方法中返回int类型的数据,将操作数栈上的c的值30返回

JMM内存模型

JMM描述的是一种抽象的概念,规定了一个线程怎么样可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
JMM是围绕原子性、有序性、可见性展开的。
在这里插入图片描述

volatile可见性实现原理

volatile修饰的变量的read、load、use操作和assign、store、write必须是连续的,即修
改后必须立即同步回主内存,使用时必须从主内存刷新,由此保证volatile变量操作对多线程
的可见性。

硬件层面的实现

在这里插入图片描述

早期使用总线锁来保证
1、共享变量X存于主内存空间中
2、CPU0从主内存中读取到数据,获取到总线锁,从L3一直读取到L1中
3、因为CPU0占用了锁,所以此时CPU1无法再次获取数据

后期使用MESI缓存一致协议
M 修改、E 独享、S 共享、I 无效
我们知道JVM创建的线程,在内核空间上也会创建一个对应的线程,最终交由cpu来执行。如上图所示,线程A对象CPU0来执行,线程B对应CPU1来执行。现在线程A、线程B要同时修改共享变量x的值,volatile保证可见性其实就是用缓存一致性协议来做的。

参考CPU结构来理解
volatile修饰X后,在硬件源语上会有一个lock前缀;CPU一直会监听bus总线里面的消息
1、共享变量X存于主内存空间中
2、CPU0从主内存中读取X,经过总线,发现被lock前缀修饰了,则会被CPU1监听到有其他CPU在读取数据。CPU0读取到数据后会在缓存中有个X副本,并标记状态E(此时只有CPU0持有这个副本)
3、CPU1从主内存中读取X,经过总线,发现被lock前缀修饰了,会被CPU0监听到,CPU0将缓存中的X副本状态修改为S,CPU1读取到数据后也会在缓存中有个X副本,此时标记状态S
4、CPU0上X存储在自己缓存中的缓存行上,CPU1上X存储在自己缓存中的缓存行上,两个CPU都对各自的存储X的缓存行进行加锁。
4.1、比如CPU0修改X=2,会先获取获取缓存行上的锁,再将状态修改为E,然后向外部发送一个本地写缓存消息;此时会被CPU1感知到,CPU1会将自己的X副本状态标记为I失效,并丢弃掉;
4.2、若CPU0、CPU1同时修改X的值呢,两个CPU都会向外部发送本地写缓存消息,两个消息都会经过bus总线,bus总线会进行总线裁决,来决定让哪个CPU来获得修改的权限。若CPU1获得,则CPU0的X副本状态标记为I失效,并丢弃掉;
5、若CPU0先修改了X的值,则会将X的值写入主内存,然后CPU1会重新从主内存中读取X的值

### JMM JVM 内存模型区别与联系 #### 定义与作用范围 Java内存模型 (JMM) 是一种抽象的概念框架,用于描述多线程环境下程序的行为以及如何处理共享变量的一致性可见性问题[^3]。而 Java 虚拟机(JVM) 的内存结构则是指运行时数据区的具体划分方式,比如堆、栈、方法区等物理存储区域。 #### 关键特性对比 ##### 可见性 JMM 提供了一套机制来保证不同线程之间操作的可见性。例如,在新版本中对于 `final` 字段的操作被赋予特殊待遇,一旦对象构建完成并初始化其所有的 final 域,则这些域的内容将立即对其他任何读取该对象引用的线程变得可见[^1]。 ##### 原子性 虽然两者都涉及到原子性的概念,但是它们关注的角度有所不同。JMM 主要是针对复合动作(如先写后读),确保某些特定类型的指令不会被拆分;相比之下,JVM 更多地是从硬件层面考虑单条机器码级别的不可分割执行[^4]。 ##### 有序性 由于现代处理器普遍支持乱序执行优化技术,因此即使源代码中的顺序固定不变,实际编译后的二进制文件也可能存在指令重排现象。为此,JMM 设定了 Happens-Before 规则集用来约束这种行为,从而维持逻辑上的因果关系链路。然而,具体到每种 CPU 架构上实现上述规则的方式会有所差异,这就属于 JVM 实现细节的一部分了。 ```java // 示例:展示 volatile 如何影响可见性有序性 public class VolatileExample { private static volatile boolean flag = false; public void writer() { flag = true; } public void reader() { while (!flag) {} System.out.println("Flag has been set"); } } ``` #### 协作关系 实际上,JMM 并不是独立存在的实体,而是作为指导原则嵌入到了各个具体的 JVM 版本当中去实施。当开发者编写遵循 JMM 规范的应用程序时,底层的 JVM 就负责将其转换成适合目标平台特性的有效形式,同时还要满足由前者所设定的各种同步需求性能期望值[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值