Java内存模型

#空杯心态,一切归零#

一:什么是java内存模型

我们通过一张图片来直观的感受下内存模型的抽象概念。

在java内存模型中,本地内存(在java里面其实就是栈空间)被线程私有,主内存是所有线程共享的。对于主内存中的共享变量,每个线程会获取一个共享变量的副本,也就是在本地内存中缓存了一份数据,在线程内部都是对副本进行操作。只有在需要写回到主存时,才会交由JMM来进行控制。如果进行JMM控制,可能在竞态条件下产生线程安全问题。

再通过一张图来看下CPU多级缓存和内存的全貌。

从图中可以看出,L1和L2以及寄存器是CPU私有,L3和主存是多个CPU共享。

补充一点,CPU读取数据时的流程说明:

(1)先读取寄存器的值,如果存在则直接读取

(2)再读取L1,如果存在则先把cache行锁住,把数据读取出来,然后解锁

(3)如果L1没有则读取L2,如果存在则先将L2中的cache行加锁,然后将数据拷贝到L1,再执行读L1的过程,最后解锁

(4)如果L2没有则读取L3,同上先加锁,再往上层依次拷贝、加锁,读取到之后依次解锁

(5)如果L3也没有数据则通知内存控制器占用总线带宽,通知内存加锁,发起内存读请求,等待回应,回应数据保存到L3(如果没有就到L2),再从L3/2到L1,再从L1到CPU,之后解除总线锁定。

回到正题,结合上面的两张图,是否发现结构有些相似。工作内存对应CPU缓存,内存对应L3和RAM,都存在私有和共享。也都采用同样的思想,通过使用缓存提升程序的性能。但是任何事情都有两面性,提升的性能的同时,由此也就衍生出了缓存一致性的问题。

二:缓存一致性是如何解决的

这里主要引出volatile关键字。它的本质是告诉JVM,禁用缓存。

在CPU层面 ,最常用到的缓存一致性协议是MESI协议。主要原理是 CPU 通过总线嗅探机制(监听)可以感知数据的变化从而将自己的缓存里的数据失效,缓存行中具体的几种状态如下:

之前是锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放,不过实际后来的处理器都采用锁缓存替代锁总线,因为锁总线的开销比较大,锁总线期间其他CPU没法访问内存。

在JVM层面,JMM通过happen-before规则和内置的八种指令来实现。

happen-before规则:本质上是可见性的问题。就是说一个线程的对共享资源的操作对另一个线程是可见的,另一个线程需要等前一个线程操作结束后,才能执行相关操作。

1、程序的顺序性规则

这条规则是指:在一个线程中,指令按顺序执行,前面的操作 Happens-Before 于后续的任意操作。

2、volatile变量规则

这条规则是指:对一个volatile变量的写操作, Happens-Before 于后续对这个变量的读操作。

3、传递性规则

这条规则是指如果A Happens-Before B,B Happens-Before C,那么A也将Happens-Before C。

4、管程中的锁的规则

这条规则是指:对一个锁的解锁 Happens-Before 于后续对这个锁的加锁操作,即,先解锁才能后加锁。

5、线程的start规则

这条规则是指:如果在线程A中启动线程B,那么该start()操作 Happens-Before 于线程B中的任意操作。

6、线程的join规则

其中的volatile变量规则,就是告诉JVM,缓存失效了,需要在使用时,从主存中获取最新的值。

另外,稍微补充下JMM提供的8条内置指令

从主存同步数据到工作内存时,需要借助read和load指令;从工作内存写回主内存时,需要用到store和write指令。其他线程要往主内存写数据时,会对缓存进行加锁,直至锁释放后才能执行相关操作。

volatile有两层含义:除了禁用缓存,还有禁止指令重排。本质是通过内存屏障来实现,为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。一个读的操作为load,写的操作为store。此处就不展开详细描述了。

三:如何解决一致性、可见性和原子性问题

synchronized和lock:在程序层面进行加锁,解决一致性问题和原子性问题。

volatile:禁用缓存,解决可见性问题。

四:对应用程序按需禁用缓存和编译优化的小建议

其实总的说来,JMM是在安全性和性能上做折中取舍,让程序中可以自己配置一些方式来实现按需禁用的目的。

final:对程序中的一些常量,使用final修饰,告诉JVM,这是一个不可变类型,不用优化。

volatile:对前后有依赖关系的逻辑,使用volatile修饰,告诉JVM,此处不用重排序。

五:写在最后

作为一个老后端程序员,第一次在平台上写技术类的文章,心中有些忐忑,也很惶恐。之前的底子也不怎么扎实,对一些技术也都掌握得不怎么成体系,很多时候,都会陷入一个误区。以为平时看到的文章或者听过的视频,自己都掌握了。但是真需要好好表达出来时候,却是又有些为难。以我自己为例,今日的这篇小文章,差不多耗费了整个下午的时光,删了写,写了删,遇到模棱两可的,再去翻阅资料。知易行难,还好,一切都有个开端,希望通过持续通过这种方式,记录自己对技术的感悟,温故而知新吧。

此处如果有小伙伴有补充的话,也欢迎在评论区留言。欢迎大家指摘,一起共勉。

相关推荐
uzong9 小时前
7 年 Java 后端,面试过程踩过的坑,我就不藏着了
java·后端·面试
J老熊14 小时前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java14 小时前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
陪学16 小时前
百度遭初创企业指控抄袭,维权还是碰瓷?
人工智能·百度·面试·职场和发展·产品运营
大数据编程之光17 小时前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
ifanatic19 小时前
[面试]-golang基础面试题总结
面试·职场和发展·golang
程序猿进阶20 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
长风清留扬21 小时前
一篇文章了解何为 “大数据治理“ 理论与实践
大数据·数据库·面试·数据治理
周三有雨1 天前
【面试题系列Vue07】Vuex是什么?使用Vuex的好处有哪些?
前端·vue.js·面试·typescript
爱米的前端小笔记1 天前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘