JMM(JAVA内存模型)是JVM基础知识中最容易被忽略的部分,但它对于我们加深对JVM总体的理解起到举足轻重的作用,同时也是大厂面试的一个重要考点。
![1c0be05255643e442294abfc51384fe8.gif](https://i-blog.csdnimg.cn/blog_migrate/853bab28e41295ba989c2ffc479287d2.gif)
目录
基本概念
性质
目的
内容
规范变量访问
规范原子性
规范可见性
规范有序性
注意事项
面试中如何回答
基本概念
JMM:JAVA Memory Model 也即JAVA内存模型
下面分四个方面来陈述:
性质:JMM本质上是一个什么东西。
目的:JMM之所以存在,是为了解决什么问题?
内容:JMM为了实现它的目的,要做些什么?
实现:利用什么技术真正实现它的目的?
JMM是一种规范。
就好像HTTP是一种交互协议,JAVA运行时数据区是一种内存结构一样,JMM是对JAVA虚拟机作出规范,使它必须符合某些要求以满足某些目的。
目的借助JMM规范,我们希望能够做到:
屏蔽硬件和操作系统的访问差异,让Java程序在各种平台下都能达到一致的内存访问效果;
解决多线程通过共享内存进行通信时,存在的原子性、可见性、有序性问题。
内容
规范变量访问JMM的首要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
![944f5eb83922eeb4712922f413f301f7.png](https://i-blog.csdnimg.cn/blog_migrate/a9a587f36513127a0f33f4e6fcbae901.png)
JMM规定了所有的变量都存储在主内存(Main Memory)中
每个线程还有自己的工作内存(Work Memory),其中保存被该线程使用到的变量的主内存副本拷贝
线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存
线程间变量值的传递需要通过主内存,不能访问其他线程的工作内存
JMM要求:
除了64位的数据类型(long和double),其他基本数据类型的访问读写都是必须具备原子性的。
不过现在的商用虚拟机都会把long和double数据类型也实现为原子操作。
另外,为了能够实现更大范围的原子性保证,JMM还提供了lock和unlock两个原子操作来满足这种需求,与它们相对应的更高层次的字节码也就是monitorenter
和moniterexit
,也就是synchronized的底层实现方式。还不是很了解synchronized?传送门:Synchronizd关键字
可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。
为了规范可见性,JMM引入了volatile关键字。volatile保证了:
被volatile修饰的变量在被修改后立即同步到主内存
被volatile修饰的变量在每次被使用之前都从主内存刷新
因此,可以使用volatile来保证多线程操作时变量的可见性。
而具体如何保证上面两条,内存屏障也起到至关重要的作用,篇幅原因这里就不详细展开了。如果想对volatile有更进一步了解,请移步Volatile关键字
除了volatile以外,还有两个关键字可以实现可见性:
synchronized
同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)”这条规则获得的
final
被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见final修饰的值。
规范程序的有序性,也即让程序指令能够按照想要的顺序来执行。
这里的程序指令并不是一行行代码,而是应该理解为一个个原子的操作指令,它是比一行行代码更细化的粒度。
JMM中利用三点来规范有序性:
volatile关键字防止指令重排
volatile是通过内存屏障来防止指令重排的,如果想对volatile有更进一步了解,请移步Volatile关键字
as-if-serial语义
含义是:“在单线程下,无论指令怎样排序,最终结果都不能被改变。”
有了as-if-serial语义,才使得synchronized也能够保证有序性。
happens-before"先行发生"原则
“先行发生”原则定义两项操作之间的偏序关系。包括了“程序次序规则”、“线程启动规则”、“传递性”等等。具体如下(简单了解即可,无需记忆):
程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构。
管程锁定规则:一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作。
volatile变量规则:对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作。
线程启动规则:Thread的start( )方法先行发生于这个线程的每一个操作。
线程终止规则:线程的所有操作都先行于此线程的终止检测。可以通过Thread.join( )方法结束、Thread.isAlive( )的返回值等手段检测线程的终止。
线程中断规则:对线程interrupt( )方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupt( )方法检测线程是否中断
对象终结规则:一个对象的初始化完成先行于发生它的finalize()方法的开始。
传递性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C。
注意事项
经常有人把JAVA内存模型和运行时数据区搞混,也就是下面两张图:
![944f5eb83922eeb4712922f413f301f7.png](https://i-blog.csdnimg.cn/blog_migrate/a9a587f36513127a0f33f4e6fcbae901.png)
![0c8dd73e500da384f985f52cc213a986.png](https://i-blog.csdnimg.cn/blog_migrate/92eabb3009349e1b6d7bc002f3a25b71.jpeg)
请千万不要再搞混了。
面试中如何回答
Q:“JMM了解吗?简单讲讲”
A: JMM,JAVA内存模型,是一种用来屏蔽各种硬件和操作系统的内存访问差异的规范,目的是解决多线程通过主内存通信时,存在的原子性、可见性、有序性问题。
JMM首先规范了变量的访问。它要求所有变量都要存在主内存中,每个线程在自己工作内存中操作变量,然后同步回主内存;
其次JMM利用内存间原子性的交互操作规范了原子性,例如lock、unlock、read、store等等。通过这些操作,JMM保证了基本数据类型操作的原子性,并支持了synchronized;
JMM还利用volatile实现了可见性,它要求被volatile修饰的变量在使用时与主存及时同步;
最后JMM还保证了有序性,这是由volatile关键字的内存屏障、as-if-serial语义,以及happens-before“先行发生”原则一同支持的。
以上是概述性回答,然后根据面试官提的问题,可以再详细展开。例如:“面试官:‘volatile怎么保证可见性的?'”就可以再讲讲主内存与工作内存的及时性同步,还有内存屏障等等。
![9367fed79c109305f18eef02d46584ba.gif](https://i-blog.csdnimg.cn/blog_migrate/16bfc2e61b73093cb3cd71bae0fb9f74.gif)
添加我的个人微信,
备注“加群”
一同技术交流:
![0426759076150da5a48a853edca3a0b7.gif](https://i-blog.csdnimg.cn/blog_migrate/3845fc7fbaf9b69582ff30f39fda8c4c.gif)
![4ad114fddd18676de0f0cc1ade8a917f.png](https://i-blog.csdnimg.cn/blog_migrate/198feba87e0a9213bc95b588f33f3104.png)