以下很大部分仅是个人理解,水平和经验都很有限,如有资深人士请指出错误

物理机的设计

物理机的处理器速度很快,而物理内存的访问速度与之相比则有几个数量级的差距,所以物理内存总是无法及时响应处理器,为了解决该问题,便引入了高速缓存,因为高速缓存比物理内存访问速度要快得多,更加接近处理器的速度。架构图如下:

那么,直接把主内存也采用高速缓存一样的硬件不就行了?

高速缓存成本更贵,功耗更高,对散热、功耗控制的要求也就更高,存储容量有限,等等因素,限制了这种方案的实施。

物理机的数据同步方案

现在的处理器都是多核的,上面也说过,主内存的读写速度太慢了,所以在主内存和处理器(准确来说是内核)之间引入了高速缓存。工作的时候,内核A 上有一条线程 A-thread 需要用到主内存中的数据a(初始值定为0),于是a会先被复制进内核1的高速缓存里,就称之为A-a吧,之后内核1对a的操作就会直接作用于A-a,与此同时,内核B 上也有一条线程 B-thread 需要用到主内存里的a,于是也如法炮制到内核B 的高速缓存里,称之为 B-a 吧。设想一个场景,A-thread要对a加1,B-thread要对a加2,那么实际执行过程中就会A-thread直接对 A-a 加 1,B-thread直接对 B-a 加 2,而且它们通常都不会马上同步回主内存。为什么呢?因为主内存太慢了呀!如果任何修改都立刻同步回主内存,那么高速缓存也就没有意义了,而且还不如直接访问主内存更快呢!所以,咱们本意是让a的结果为3,实际结果却是a在内核A上等于1,在内核B上等于2,在主内存里等于0!此时若有其他内核需要访问a,岂不就继续错下去了?可实际并没有世界大乱呀!那么必然有一套同步机制存在。什么时候才会同步回主内存呢?会有多种触发场景,比如内核的缓存满了,需要清理下旧数据,就把一些旧数据同步回主内存;还有一些缓存一致性协议规定的场景,或者遇到了一些CPU的指令强制它同步回主内存,等等,而这些同步规则(即缓存何时写回主内存、何时使其他核心的缓存失效)是由 CPU 硬件架构 决定的(如 x86、ARM、RISC-V 的内存模型)。与之对应的操作系统则会提供各种对应的命令来驱动硬件的同步机制。其实,不止高速缓存里存在数据同步的情况,还有代码乱序执行(Java里叫指令重排):为了使处理器里的运算单元能尽量被充分利用,输入的代码可能并一定是按照书写的顺序执行的,这种场合就也需要一套机制来保证虽然代码执行顺序是乱的,但要保证结果是对的。

Java内存模型

《深入理解Java虚拟机》一书中提到(也是网上很多抄来抄去的文章的源头):Java虚拟机规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。反正我看得雨里雾里的,还是得不停搜索其他资料才能更好理解。

为了更好的理解Java的内存模型,可以这样认为,如果用C++这种跨平台差(实际已大幅改善)的语言编写程序就需要直接或间接地在程序里启用不同的命令或API来适配不同硬件架构和操作系统,不然就会出现上述数据不同步的问题。而Java内存模型对这种针对不同硬件调用不同指令的同步方案做了进一步的抽象和封装,它使得Java程序员在开发程序时完全不必关心底层的硬件和操作系统,只需面向Java API开发,一次编写,全平台通用。为了更方便的理解,你可以极端一下:其他语言开发程序需要程序员在不同的硬件平台上调用不同的同步指令来保证数据同步,而Java弄了个内存模型,Java程序员不需要考虑任何硬件差异、操作系统差异,Java程序员只需调用Java的API就行——全平台都通用的API。

Java内存模型架构如下图

是不是跟硬件的内存模型图很像?Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这种的底层细节。

Java内存模型规定了所有的变量都存储在主内存(Java内存模型中的主内存,不是物理机上的主内存)。每条线程又有自己的工作内存(相当于物理机上的高速缓存)。线程对变量的所有操作都必须在工作内存内进行,不可直接读写主内存。不同线程之间的变量值传递都必须通过主内存。

Java内存模型的主内存和工作内存只是逻辑上的一种抽象表达,是一种规范,与硬件没有任何关系,但是任何规范都是要实施起来才有意义嘛,而且Java内存模型就是为了解决硬件差异才提出来的,它的原型就是硬件,所以Java内存模型在落地成实际应用的时候,主内存通常就是采用物理机上的主内存硬件,而工作内存就是采用物理机上的高速缓存硬件。少数场景可能会映射到寄存器上,但是大体上都是对应的。

为什么Java内存模型能屏蔽掉硬件和操作系统的内存访问差异?

Java内存模型定义了8种基础操作:lock(锁定)、unlock(解锁)、read、load、use、assign(赋值)、store、write,在此基础上又衍生出一系列的规则,例如,不允许一个线程丢弃它的最近的assign(赋值)操作,即变量在工作内存中改变了之后必须把该变化同步回主内存;不允许一个变量从主内存读取了但是工作内存却不接受;一个变量在被使用之前一定要先赋值;某些操作前,必须从主内存读取最新值,等等。简而言之:Java内存模型通过一些列严格的规定,确保了数据不会出错,同时又能兼容各类型的硬件差异。程序员在开发程序时,只需调用 volatile 、synchronized等各类Java API,JVM会对这些API的执行针对不同硬件自动完成差异化适配,确保数据同步无差错。

那,它们的关系就大致是这样了

深度思考

既然Java内存模型只是一种规范,是一种抽象逻辑,为什么也要跟物理机似的分层呢?只弄一层不行吗?

Java内存模型出现的目的是什么?是为了解决硬件的差异,虽然硬件和指令都有差异,但是硬件的架构都是  CPU-高速缓存-主内存  的架构,Java内存模型就是为了在这个架构的基础上抽取出共性,然后以一种规范和抽象逻辑的形式抹平它们之间的差异,搭建出一套通用的模式,所以Java内存模型通配硬件架构的设计初衷就注定了它无法彻底摆脱硬件原型的影响。如果只弄一层,在程序要往硬件上运行的时候反倒需要更多的转换、映射,要考虑更多的兼容性,无论性能和设计难度都会更加不理想。

像嵌入式设备,硬件和系统都很简单,可能都没有高速缓存这一层,可能只有单核。Java内存模型还适用吗?

可以使用,只是可能会出现一些专门的适配。比如,直接操作内存、软件模拟缓存一致性等手段,带来的不利影响就是性能很低,不过嵌入式设备本来也不要求多高的性能。Java内存模型是一个妥协的结果,具有广泛普遍的适用性,但在少数场合可能会有一些冗余或不够极致。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐