JMM(Java Memory Model java内存模型

目标:

搞清楚高并发场景下,java内存模型是怎么支持的,对象在内存中是怎么布局的?

 

目录

目标:

搞清楚高并发场景下,java内存模型是怎么支持的,对象在内存中是怎么布局的?

1.硬件层的并发优化基础知识

 2.多核CPU线程访问条件下数据不一致性问题?

 原因:

 解决方案:

缓存行

MESI带来的伪共享问题

3.CPU的乱序执行问题

 CPU乱序执行的原因

CPU乱序执行的表现

读指令的乱序执行

写指令的合并执行(write combining)

如何保证特定情况下,不乱序

CPU级别采用内存屏障保证或者Java的汇编指令 Lock :

JVM级别再进行规范

Java代码级别采用volatitle关键字保障

1.字节码层面(.class文件)

2.JVM层面

3. os和硬件层面


1.硬件层的并发优化基础知识

备注:

L3高速缓存也是在主板上,所以也是被所有的CPU共享的。

L0寄存器,存储着CPU内部核心的几个计算单元,用于计算。

离CPU越近容量越小,成本越高,速度越快。

比如说 有个变量int a=3;在 L2上,计算时CPU会先把a  load到L1上,然后被L0直接加载计算。

 2.多核CPU线程访问条件下数据不一致性问题?

 原因:

两个CPU彼此独立,但是都是操作CPU的共享区域的数据,比如一个CPU 对X作了修改,另一CPU是不能及时知道的。这就造成了共享区域的数据对各个不同的CPU 来说数据不一致问题。

 解决方案:

老的CPU:

总线锁会锁住总线,使得其他CPU甚至不能访问内存中其他的地址,因而效率较低

 新的CPU

现在CPU的数据一致性,通过缓存锁+总线锁来解决

https://www.cnblogs.com/z00377750/p/9180644.html

新的CPU采用缓存锁(缓存一致性)来解决,一致性的协议又很多,Intel采用了MESI协议。

 CPU会对缓存的内容做状态标记,如果读取的内容相对于主存来说被更改过(自己修改过),则状态改这个内容对应的缓存行状态改为Modified(别的cpu对这个内容所在缓存行状态就是Invalid);如果读取的内容自己独享,就将其所在缓存行状态改为Exclusive。如果读取的内容,我读的时候别人也在读取,该内容所在缓存行状态就修改为Shared。如果读取的内容别的cPU已经修改过,说明我的读的内容无效了,该内容所在缓存行  状态就变为Invalid。

缓存行

现在cpu 缓存数据,不是一个字节一个字节来缓存,而是以一个缓存行,默认64字节。一次性加载到缓存中。

MESI带来的伪共享问题

如果自己要修改某个数据A,发现它的状态已经变为了Invalid,则去缓存中再加载一次。

思考这个场景: x,y位于同一个缓存行,cpu1只想的读取x,cpu2只想读取y
由于读取时必须以缓存行为单位,cpu1 读取x时,也读取到了y。 cpu1改变了x的值后,x所在的缓存行的状态在CPU1中就会被改变为modified, 在cpu2中x所在的缓存行状态就会变为invalid。 因此,cpu2 本来想读取y,一看y 所在缓存行状态变为了 invalid 。就会去重新加载y所在的缓存行。问题是cpu2其实只关注y,y的值也没有变化,而x的变化和它无关。因此本质上cpu2不需要去进行这次缓存加载的。
同理cpu2对y作了修改,后cpu1也得重新加载x所对应的缓存行。
这样的重新加载是一种时间和资源的浪费,缓存一致性或者说缓存锁解决数据安全问题的的同时带来的 这个多次非必要加载缓存问题被称为伪共享问题。

3.CPU的乱序执行问题

首先明确一个事实,CPU执行指令的时候是乱序执行的。

 CPU乱序执行的原因

https://www.cnblogs.com/liushaodong/p/4777308.html

CPU的执行指令在内存中做一些操作,因为CPU的执行速度相较于内存至少时100倍,所以CPU不能干等着执行结果,为了提高效率,CPU就 在一条指令执行结束之前,继续执行别的指令。这种不按照程序的编写顺序执行的情况,被称为CPU的乱序执行问题。乱序变现在读指令的乱序,和写指令的乱序。

当然乱序执行不能带来程序的最终执行结果的乱序。所以,计算机底层,对CPU的乱序执行也是作了规范,也保障乱序执行的结果是正确的。提高了执行效率,也保证执行结果的正确性。

CPU乱序执行的表现

读指令的乱序执行

CPU在执行一条读指令后等待该指令执行完(或者返回结果)前,回去执行另一条与该指令没有依赖关系的读指令

写指令的合并执行(write combining)

合并写:

CPU将计算好的值A缓存到L1,如果缓存失败,就会缓存到L2,再此过程中L2相对于CPU太慢了,如果这个过程中A值被改写了几次。CPU会直接将改写的最终结果刷到L2。这种情况被称为合并写

如何保证特定情况下,不乱序

某些特定场景写要保证顺序,如何保障有序性

CPU级别采用内存屏障保证或者Java的汇编指令 Lock :

sfence:在sfence指令前的写操作当必须在sfence指令后的写操作前完成。
lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成。
mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操
作前完成。
原子指令,如x86上的”lock …” 指令是一个FullBarrier,执行时会锁住内存子系统来确保执行顺
序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持
程序顺序

JVM级别再进行规范

LoadLoad屏障: 对于这样的语句Load1; LoadLoad; Load2,
在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:
对于这样的语句Store1; StoreStore; Store2,
在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:
对于这样的语句Load1; LoadStore; Store2,
在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障: 对于这样的语句Store1; StoreLoad; Load2,
​ 在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。

 备注:这些规范的实现,是依赖于硬件级别的原理来实现的。具体用哪些硬件原理,怎么组合操作,JVM自己决定。

Java代码级别采用volatitle关键字保障

1.字节码层面(.class文件)

被volatile 修饰的变量在.class二进制字节码文件中多了一个acc_volatile 标记

2.JVM层面

JVM对被acc_volatile  修饰的变量,也即 .class文件中被 acc_volatile修饰的变量:

        如果它是写操作指令,则在该写操作前面加一个 StoreStoreBarrier ,后面加一个StoreLoadBarrier ,保证该指令与上下两个指令不重排序

        如果它是读操作指令,则在该读操作前面加一个 LoadLoadBarrier ,后面加一个LoadStoreBarrier ,保证该指令与上下两个指令不重排序

3. os和硬件层面

windows 上采用Lock 指令和MESI 实现,Linux我忘了,哈哈哈

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值