Open zgq105 opened 4 years ago
内部Java内存模型 JVM内部使用的是java内存模型在线程堆栈和堆之间分配内存。如下图所示:
如果把线程堆栈的调用过程考虑进来,那么就得到如下图所示:
jvm内存模型满足以下特性:
硬件内存架构 现代硬件内存体系结构与内部Java内存模型有所不同。硬件架构内存模型是计算机真是存在物体体系架构;而java内存模型是JVM抽象模型。具体的硬件架构如下所示:
现代计算机中一般会有多个CPU或者多核CPU,因此存在同时运行多个线程的情况。每个CPU有一组寄存器,CPU寄存器本质上是CPU内存。CPU在寄存器上的执行速度要远快于在主内存上的执行速度。
每个CPU可能还会存在高速缓存层。实际上,大多数现代CPU都具有一定大小的缓存层。CPU可以比其主存储器更快地访问其高速缓存,但是通常不如它可以访问其内部寄存器的速度快。比如,我的计算机是有三级缓存,如下所示:
综上所述,CPU执行速度:寄存器>CPU缓存>主存储器。
结合Java内存模型和硬件内存体系结构 java内存模型和硬件内存体系是不同的。硬件内存架构是不区分栈和堆的。在硬件上,线程的栈和堆都位于主内存中,有时部分栈和堆还会出现在CPU缓存或者寄存器中。如下所示:
线程共享数据主要包括以下数据:
JVM内存交互协议 线程和主内存之间的交互过程rux如下所示:
java内存模型定义了一个8种操作来完成线程之间的交互,如下:
为什么要指令重排序? 在java中,java编译的过程中和处理器执行指令过程中可能会出现指令重排序。指令重排的作用是提升程序的执行效率。因为CPU执行指令,本质上是流水线作业,可以通过调整执行的顺序来优化工作的路程,从而提升程序的执行效率。 代码片段1
int a = 1; int b = 1; a = a + 1; b = b +1 ;
代码片段2
int a = 1; a = a + 1; int b = 1; b = b +1 ;
从上面的两段代码中,我们知道代码片段2的执行效率会高于代码片段1;因为代码片段2把相似功能单元的指令接连执行来减少流水线中断的情况。
编译器重排序会按JMM的规范严格进行,换言之编译器重排序一般不会对程序的正确逻辑造成影响。但是处理器重排序就需要使用到内存屏障了。
指令重排遵循的规则 单线程情况下,当存在数据依赖时,指令不会发生重排。如下情形所示:
以上情况,指令不会重排;但是在多线程环境下失效。
什么情形需要禁止指令重排? 在多线程环境时,指令重排会产生不确定的执行效果。如下面代码所示:
public class ReorderExample { int a = 0; boolean flag = false; public void writer() { a = 1; //1 flag = true; //2 } public void reader() { if (flag) { //3 int i = a * a; //4 System.out.println(i); } } }
说明:由于1和2是没有数据依赖的,故可能会发生指令重排的;如果执行的指令重排,则执行的结果就有多种结果了。因此,在多线程环境中,需要注意指令重排造成的逻辑不一致的问题。
如何防止指令重排 在java中,volatile关键字可以保证变量的可见性和防止指令重排。关于volatile详细用法,这里暂时不展开阐述。
什么是内存屏障? 内存屏障是jvm中的一种规则,是一种CPU指令;用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。volatile关键字就是通过内存屏障实现了可见性和有序性。
1. JMM内存模型
1.1 java内存模型和硬件架构模型
内部Java内存模型 JVM内部使用的是java内存模型在线程堆栈和堆之间分配内存。如下图所示:
如果把线程堆栈的调用过程考虑进来,那么就得到如下图所示:
jvm内存模型满足以下特性:
硬件内存架构 现代硬件内存体系结构与内部Java内存模型有所不同。硬件架构内存模型是计算机真是存在物体体系架构;而java内存模型是JVM抽象模型。具体的硬件架构如下所示:
现代计算机中一般会有多个CPU或者多核CPU,因此存在同时运行多个线程的情况。每个CPU有一组寄存器,CPU寄存器本质上是CPU内存。CPU在寄存器上的执行速度要远快于在主内存上的执行速度。
每个CPU可能还会存在高速缓存层。实际上,大多数现代CPU都具有一定大小的缓存层。CPU可以比其主存储器更快地访问其高速缓存,但是通常不如它可以访问其内部寄存器的速度快。比如,我的计算机是有三级缓存,如下所示:
综上所述,CPU执行速度:寄存器>CPU缓存>主存储器。
结合Java内存模型和硬件内存体系结构 java内存模型和硬件内存体系是不同的。硬件内存架构是不区分栈和堆的。在硬件上,线程的栈和堆都位于主内存中,有时部分栈和堆还会出现在CPU缓存或者寄存器中。如下所示:
1.2 线程共享数据
线程共享数据主要包括以下数据:
1.3 线程私有数据
1.4 工作内存和主内存通信
JVM内存交互协议 线程和主内存之间的交互过程rux如下所示:
java内存模型定义了一个8种操作来完成线程之间的交互,如下:
2. 重排序
为什么要指令重排序? 在java中,java编译的过程中和处理器执行指令过程中可能会出现指令重排序。指令重排的作用是提升程序的执行效率。因为CPU执行指令,本质上是流水线作业,可以通过调整执行的顺序来优化工作的路程,从而提升程序的执行效率。 代码片段1
代码片段2
从上面的两段代码中,我们知道代码片段2的执行效率会高于代码片段1;因为代码片段2把相似功能单元的指令接连执行来减少流水线中断的情况。
编译器重排序会按JMM的规范严格进行,换言之编译器重排序一般不会对程序的正确逻辑造成影响。但是处理器重排序就需要使用到内存屏障了。
指令重排遵循的规则 单线程情况下,当存在数据依赖时,指令不会发生重排。如下情形所示:
以上情况,指令不会重排;但是在多线程环境下失效。
什么情形需要禁止指令重排? 在多线程环境时,指令重排会产生不确定的执行效果。如下面代码所示:
说明:由于1和2是没有数据依赖的,故可能会发生指令重排的;如果执行的指令重排,则执行的结果就有多种结果了。因此,在多线程环境中,需要注意指令重排造成的逻辑不一致的问题。
如何防止指令重排 在java中,volatile关键字可以保证变量的可见性和防止指令重排。关于volatile详细用法,这里暂时不展开阐述。
什么是内存屏障? 内存屏障是jvm中的一种规则,是一种CPU指令;用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。volatile关键字就是通过内存屏障实现了可见性和有序性。
3. happens-before原则