JVM系列(2)--JMM-Java内存模型

先放张存储器的层次结构图,没别的意思,就是给你康康
在这里插入图片描述

1. 缓存一致性问题

1. 为什么会产生数据不一致?

L1和L2在cpu内部,当不同cpu修改了这个数据,其他cpu缓存里如果有这个数据,那就不一致了

2. 伪共享问题

读取缓存以缓存行 cache line为基本单位,一般64bytes,当同一缓存行中的不同数据分别被不同cpu锁定,就会互相产生伪共享

3. 解决方案1

总线锁

4. 解决方案2

不同类型cpu有不同的一致性协议,intel的cpu用的是MESI协议,有些场景还是要用总线锁

现代cpu的数据一致性解决方案:缓存锁(MESI等等)+ 总线锁
详情请见

2. cpu的乱序执行问题

1. 为啥要乱序?

为了提升效率

  • 读指令的同时可以同时执行不受影响的其他指令
  • 写指令的同时可以进行合并写(WCBuffer)

所以会导致cpu乱序执行

2. 啥是合并写?

cpu在给某值做计算时,需要先把结果写回到L1,如果L1里没有这个值的缓存,就会写道L2里,但因为L2比较慢,所以如果写的过程中该值后续还会被改变,就会把这个执行过程合并到一起放到WCBuffer中,最终计算到L2中

3. cpu乱序执行的解决方案

1.硬件层面
  1. x86 cpu内存屏障
  • sfence:(save)在sfence指令前的写操作必须在该指令后的写操作前完成
  • lfence:(load)在sfence指令前的读操作必须在该指令后的读操作前完成
  • mfence: (mix)在sfence指令前的读写操作必须在该指令后的读写操作前完成
  1. intel lock汇编指令

lock ...指令是一个原子指令,Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至可以跨越多个cpu

2. 软件层面(JVM)
  1. JVM做了相关的规范,实现依赖于硬件层面的实现
  2. 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的写入操作被其他处理器可见

4. volatile的实现

1. 字节码层面

示例如下,查看翻译后的字节码,发现只是在j这个变量前加了volatile的访问修饰符即ACC_VOLATILE

public class TestVolatile {
    int i;
    volatile int j;

    public TestVolatile() {
    }
}

在这里插入图片描述

2. JVM层面

JVM对所有 内存中 volatile修饰的变量 的

  • 写操作之前加了StoreStoreBarrier,之后加了StoreLoadBarrier
  • 读操作之前加了LoadLoadBarrier,之后加了LoadStoreBarrier
3. OS和硬件层面

windows上是用上文中的lock实现的

4. volatile实现细节总结
  • 首先写源码时,在class文件中给被volatile修饰的变量添加了ACC_VOLATILE 访问修饰符
  • JVM读到这个修饰符时,会在内存读写之前都加屏障
  • JVM和OS去执行这个程序时,在windows上使用lock指令实现,linux上使用了屏障+lock,详情自查

5. synchronized的实现

1. 字节码层面
  1. 如果是方法,就增加ACC_SYNCHRONIZED修饰符
  2. 如果是同步语句块,使用monitorentermonitorexit 指令
  • 示例如下,有两条monitorexit 是因为产生异常后会自动退出
public class TestSync {
    public TestSync() {
    }

    synchronized void m() {
    }

    void n() {
        synchronized(this) {
            ;
        }
    }

    public static void main(String[] args) {
    }
}

在这里插入图片描述

2. JVM层面

C/C++调用了操作系统提供的同步机制

3. OS和硬件层面

x86: lock cmpxchg xxxx ,cpu使用lock指令实现

  • cmpxchg指令前如果加了lock,那就代表后面的指令执行过程中,这块区域会被锁定,只有本指令可以修改,其他指令不可以修改该区域的数据

6. Java 八个原子操作

已弃用,但JMM没有改变这种描述

  • lock:主内存,表示变量为线程独占
  • unlock:主内存,解锁线程独占变量
  • read:主内存,读取内容到工作内存
  • load:工作内存,read后的值放入线程本地变量副本中
  • use:工作内存,传值给执行引擎
  • assign:工作内存,执行引擎的结果赋值给线程本地变量
  • store:工作内存,存值到主内存给write备用
  • write:主内存,写变量值

7. 一些概念

happens-before

JVM规定重排序必须遵守的规则

as if serial

不管如何重排序,只用保证最后结果不变就行

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值