JMM(Java Memory Model)即Java内存模型,更多体现为一种规范和规则。该规范定义了一个线程对共享变量的写入时,如何确保对另一个线程是可见的。
JMM虽然并不像JVM内存结构一样是真实存在的运行实体,但其具备以下价值:
JMM将所有的变量都存放在公共主内存中,当线程使用变量时,会把公共主内存里的变量复制到自己的私有工作内存中,线程对变量的读写操作,实际操作的是自己工作内存中的变量副本。因此JMM模型也需要解决代码重排序和缓存可见性问题。如何解决呢,JMM提供的方案包括volatile、synchronized、final等等。JMM定义了一些内存操作的抽象指令集,然后将这些抽象指令包含到volatile、synchronized等关键字的语义中,并要求JVM在实现这些关键字时必须具备其包含的JMM抽象指令的能力。
JMM是属于语言级别的内存模型,它确保了在不同的编译器和不同的处理平台上,为Java程序员提供一致的内存可见性保证和指令并发执行的有序性。JMM属于概念和规范维度的模型,是一个参考性质的模型,定义了一个指令集、一个虚拟计算架构、一个执行模型。具体的JVM需要遵循JMM的定义进行实现,它能够运行根据JMM模型指令集编写的代码,就像真机可以运行机器代码一样。
JVM是Java虚拟机,虽然也是一个概念和规范维度的模型,但通常将JVM理解为实体的、实现维度的虚拟机,一把指HotSpot VM。JVM在执行Java程序时,会把所管理的内存划分为若干个不同的数据区域,每个区域用途不同,有些区域随着虚拟机进程的启动而存在,有些区域依赖用户线程的启动和结束而建立和销毁。《Java虚拟机规范(Java SE 8)》中描述的JVM运行时内存区域结构如图:
多线程的执行最终都会映射到硬件处理器上执行,但是JMM与硬件内存架构并不完全一致。对于硬件来说,只有寄存器、高速缓存、主存的概念,并没有工作内存(线程私有数据区)和主内存(堆内存)之分,也就是说JMM对内存的划分对硬件内存没有任何影响,因为JMM只是一种抽象的概念,是一组规则,并不实际存在。无论是JMM工作内存还是主内存的数据,对于计算机硬件来说都会存储在计算机主存中,也可能存储到CPU高速缓存、或者寄存器中。总体来说,JMM和计算机硬件内存架构是一个相互交叉的关系,是一种抽象概念的划分与真实物理硬件的交叉。
JMM定义了一套自己的主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存,又是如何从工作内存写入主内存。该协议有8中操作,要求JVM在实现时必须保证其中每一个操作都是原子的。这8种操作是:
简单来说,把一个变量从主内存拷贝到工作内存,就要按顺序执行Read和Load操作;线程通过Use和Assign操作对变量进行计算处理;如果要把变量从工作内存同步会主内存,就要按顺序执行Store和Write操作。
注意:JMM要求Read和Load、Store和Write不能单独出现,其必须按顺序执行,但不要求是连续执行,即在Read和Load之间、Store和Write之间可以插入其他指令。
以上JMM的8个操作规范定义相当严谨,也极为繁琐,JVM实现起来也非常复杂。Java设计团队在新的JMM版本中不断地对这些操作进行简化,如将8个操作简化为Read、Write、Lock、UnLock四个操作。虽然进行了简化,但是JMM的基础设计并未改变。
JMM提供了自己的内存屏障指令,要求JVM编译器实现这些指令,禁止特定类型的编译器和处理器重排序,从而解决了顺序一致性问题。
JMM定义了不对应任何处理器的JMM逻辑层内存屏障,来屏蔽底层CPU硬件平台的差异。主要有Load和Store两类:
在实际使用中,对Load Barrier和Store Barrier两类屏障进行组合,组合成四个屏障,用于禁止特定类型的处理器重排序:
Load1;LoadLoad;Load2
该示例的含义为:
在Load2要读取的数据被访问前,使用LoadLoad屏障保证Load1要读取的数据被读取完毕。
该指令会让【编译器】和【处理器】禁止对其前后的指令进行重排序;
该指令会让高速缓存中的数据失效,重新从主内存加载数据
Store1;StoreStore;Store2;
该示例的含义为:
在Store2及后续写入操作执行前,使用StoreStore屏障保证Store1的写入结果对其他处理器可见。
该指令会让【编译器】和【处理器】禁止对其前后的指令进行重排序;
该指令会让高速缓存从的最新数据写回到主内存;
Load1;LoadStore;Store2;
该示例的含义为:
在Store2及后续写入操作执行前,使用LoadStore屏障保证Load1要读取的数据被读取完毕。
该指令会让【编译器】和【处理器】禁止对其前后的指令进行重排序;
该指令会让高速缓存中的数据失效,重新从主内存加载数据;
Store1;StoreLoad;Load2
该示例的含义为:
在Load2及后续所有读取操作执行前,使用StoreLoad屏障保证Store1的写入结果对所有处理器可见。
该指令会让【编译器】和【处理器】禁止对其前后的指令进行重排序;
该指令会让高速缓冲中Store1指令结果【写回主内存】;
该指令会让高速缓存中Load2执行重新从主内存加载数据
JMM中的四个内存屏障的性能开销是不一样的。
LoadLoad、StoreStore两个屏障的性能最高。因为在这两个屏障的上下文中,高速缓存和主内存只需要一种类型的交互即可完成,JMM只需保证同类型交互的先后顺序即可,缓存数据一致性的维护工作量相对较小;LoadStore的性能相对较低。此屏障要求数据加载执行在前,数据写入执行在后,只要求了被加载数据的可见性,没有要求后面写入数据的可见性,所以,该屏障没有缓存数据一致性的维护工作量。另外,该屏障限制了Store不能重排到Load之前。
StoreLoad的性能最低,原因是:需要维护缓存数据一致性。
对应到物理硬件平台上,JMM的StoreLoad屏障,最终会编译成硬件层面的全屏障(Full Barrier)。而全屏障不仅仅要让寄存器、高速缓存中的最新数据写回到主内存,还要让高速缓存中的数据失效,重新从主内存加载数据,另外,还全方位的禁止了对屏障指令前后的Store/Load指令进行重排序。所以StoreLoad屏障的性能最低、开销最大。
JMM定义了一套自己的规则:Happens-Before(先行发生)规则,且确保两个Java语句之间必须存在Happens-Before关系。JMM尽量确保这两个语句之间的内存可见性和指令有序性。
Happens-Before规则主要包括一下几个方面:
留言与评论(共有 0 条评论) “” |