目录
前言
上一篇笔记介绍了Java内存模型硬件层面的支持,这篇笔记记录下JVM规范中对JMM的有哪些主要内容。
一、主内存和工作内存
前提:Java内存模型的主要目的是定义程序中各种变量的访问规则。这里说的变量只涉及共享变量,如实例字段、 静态字段和构成数组对象的元素。但是不包括局部变量与方法参数,因为是线程私有的,不会被共享,不会存在竞争问题。
Java内存模型规定了所有的变量都存储在主内存,每个线程还有自己的工作内存。 线程的工作内存中保存了被该线程使用的共享变量的主内存副本, 线程对共享变量的所有操作(读取、 赋值等) 都必须在工作内存中进行,而不能直接读写主内存中的数据,不同的线程之间也无法直接访问对方工作内存中的变量, 线程间共享变量值的传递均需要通过主内存来完成。
线程、 主内存、 工作内存三者的交互关系如下图:
二、指令重排序
为了发挥处理器在运算的最大效率,处理器会采用乱序执行优化,与此类似,Java虚拟机的编译器中也有指令重排序优化,指令重排序会有线程安全问题。处理器的指令重排序会通过插入内存屏障指令来解决;编译器的指令重排序也有规范。如何保证特定情况下不乱序:
1、硬件内存屏障
CPU提供了三个汇编指令串行化运行读写指令达到实现保证读写有序性的目的:
SFENCE:在该指令前的写操作必须在该指令后的写操作前完成。
LFENCE:在该指令前的读操作必须在该指令后的读操作前完成。
MFENCE:在该指令前的读写操作必须在该指令后的读写操作前完成。
2、JVM如何规范(JSR133)
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。
3、小结一下
比如:volatile修饰的字段,在汇编指令方面没有用FENCE指令,用了lock相关的指令;在字节码方面用ACC_VOLATILE;在jvm规范方面内存区的读写都加了如下屏障:
StoreStore屏障
volatile写操作
StoreLoad屏障
LoadLoad屏障
volatile读操作
LoadStore屏障
三、#lock
再往下挖一层,会发现volatile关键字,转换成指令以后,会有一个#lock前缀...原来以为会有相应的内存屏障指令,说好的内存屏障的那些呢?
后来参考了资料Does lock xchg have the same behavior as mfence?文章以后才了解到,任何带有lock前缀的指令以及CPUID等指令都有内存屏障的作用。
这块内容比较验证,后面继续研究,希望起到抛砖引玉作用。
四、HappenBefore原则
JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。
参考:关于volatile、MESI、内存屏障、#Lock_风起尘落的博客-CSDN博客
《深入理解Java虚拟机》