天天看点

一文读懂内存模型

目录

名词解释

CPU

内存

虚拟内存

物理内存

计算机内存模型

Java内存模型

JMM八大原子操作

JMM八项规定

JMM如何保证三大特性

名词解释

在学习JMM之前,勾勾花了一点时间了解几个常见名词,都是来自百度百科的解释喔。

CPU

中央处理器(CPU),其功能主要是解释计算机指令以及处理计算机软件中的数据。CPU 是计算机的运算和控制核心,计算机系统中所有软件层的操作,最终都将通过指令集映射为CPU的操作。

内存

内存又称主存。它是CPU能直接寻址的存储空间,用于暂时存放CPU中的运算数据,与硬盘等外部存储器交换的数据。只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。

虚拟内存

虚拟内存则是指将硬盘的一块区域划分来作为内存。

物理内存

物理内存(Physical memory)是相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间。

计算机内存模型

计算机的存储设备与处理器的运算速度有几个数量级的差别,所以现代计算机都不得不加入一层读写速度更接近处理器运算速度的高速缓存Cache来作为内存与处理器之间的缓冲。

勾勾打开自己电脑的任务管理器,看到了如下图所示的内容:

一文读懂内存模型

一级缓存(L1 Cache)、二级缓存(L2 Cache)、三级缓存(L3 Cache)都称为高速缓存,它位于CPU与内存之间,是一个读写速度比内存更快的存储器。当CPU向内存中写入或读出数据时,这个数据也被存储进高速缓冲存储器中,当CPU再次需要这些数据时,CPU就从Cache读取数据,而不是访问较慢的内存,当然,如需要的数据在Cache中没有,CPU会再去读取内存中的数据。

Cache的作用:将运算需要的数据复制到缓存中,让运算能快速运行,当运算结束后再从缓存同步到内存之中,这样处理器就无需等待内存缓慢的读写了。

Cache带来的问题:缓存一致性(Cache Coherence)。通过上图可以看到,勾勾的电脑是8处理器,每个处理器都有自己的高速缓存,但是内存只有一份,这个时候如果多处理器的运算任务涉及到了同一块内存区域,那么写入的时候以谁的为准就说不定了,这个时候就需要各个处理器访问缓存时遵循一些协议。

缓存一致性问题解决:为了解决缓存一致性问题,引入了很多协议,多处理器在访问缓存时需遵循一些协议,最常用的是Inter的MESI协议,还有一些其他的协议如:MSI,MOSI,Synapse,Firefly,Dragon,Protocol等。

在理解MESI协议之前,勾勾先了解Cache Line。

Cache Line:是CPU高速缓存中的最小单位,当从内存中取单元到Cache中时,会一次取一个Cache Line大小的内存区域到Cache中,然后存进相应的Cache Line中。

那什么是MESI协议呢,它是怎么实现缓存一致性呢?

M(Modified)修改:该Cache Line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。此时其他Cache中的数据是失效状态,在数据被写入主内存且状态为S之前,其他对该缓存行的操作都需要被延迟。

E(Exclusive)独占:该Cache Line有效,数据与内存中的数据一致,数据只存在于本Cache中。此时如果其他CPU也读主存的该缓存行数据,则缓存行的状态为S。

S(shared)共享:该Cache Line有效,数据与内存中的数据一致,数据存在于多个Cache中。缓存行也必须监听其它CPU使该缓存行无效或者独享该缓存行的请求,并将该缓存行变成无效(Invalid)。

I(Invalid)无效:该CacheLine 无效,CPU在进行该变量的读取操作时不得不从主内存再次获取数据。

各个模块之间的协助如下图:

一文读懂内存模型

Java内存模型

Java内存模型(Java Memory Mode,缩写JMM),是Java虚拟机用来屏蔽掉各种硬件和操作系统内存的访问差异定义的模型,从而实现Java程序在各种平台下都能达到一致的内存访问效果。

JMM描述了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

这里的变量包括了实例字段、静态字段和构成数组元素的对象,但是不包括局部变量与方法参数,因为这些都是线程私有的,不会被共享,不存在竞争关系。只有当多个线程需要共享数据时,才必须协调他们之间的操作。

我们先看一张图:

一文读懂内存模型

JMM规定:JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中,对所有线程进行共享,此处的主内存只是虚拟机内存的一部分。每个线程又存在自己的工作内存(Working Memory),工作内存中保存的是线程使用到的变量在主存中的副本拷贝,线程对所有变量的操作(读取,赋值等)都是发生在工作内存中,线程之间是不能直接相互访问,线程间变量值的传递都需要在主内存中完成。

JMM八大原子操作

lock(锁定):作用于主内存中的变量,它把一个变量标识为一条线程独占的状态。

unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

read(读取):作用于主内存中的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。

load(载入):作用于工作内存中的变量,它把read操作从主内存得到的变量值放入工作内存的变量副本中。

use(使用):作用于工作内存中的变量,它把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码命令时将会执行这个操作。

assign(赋值):作用于工作内存中的变量,它把从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时将执行这个操作。

store(存储):作用于工作内存中的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。

write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

结合CPU、Cache、主存、工作内存,勾勾画了一个图描述x=1被线程修改为x=2的过程,勾勾只展示了一个线程的过程:

一文读懂内存模型

勾勾在第一次学习的时候曾经在这里绕晕了,把MESI和JMM的功能混淆了,从作用上来看他们都是可以用来解决数据一致性问题的,一个针对Cache,一个针对工作内存,Cache是可以拿来和工作内存类比的,只是MESI解决的是计算机硬件文件,JMM是Java本身的模型规范,所以勾勾在学习笔记种专门分开来写的。不知道勾勾的理解对不对,如果你有其他观点,可以一起交流。

如果在学习中对一个知识点不是很明白,那就多学几遍,带着思考的学习,勾勾相信每一遍的学习都会为你带来收获,最后就发现我当时好笨呀,这么简单的问题都没明白。

也许在以后的某一天,勾勾回头来看自己写的文章也会嫌弃自己写的都好浅显,但勾勾相信那个时候的勾勾一定又进步了好多,所有的结果都只是中间态,生命不止,学习不止。

JMM八项规定

  • 不允许read和load、store和write操作单独出现,即不允许一个变量从主存中读取了但是工作内存不接受,或者从工作内存发起了回写但是主内存不接受的情况。
  • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存改变了之后必须把改变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存。
  • 一个新的变量只能在主内存中产生,不允许在工作内存中直接使用一个未被初始化的变量,即对一个变量在执行user、stroe之前必须执行过assign和load操作。
  • 一个变量在同一个时刻只允许一条线程对其进行lock操作,同一个线程可以lock多次,但是也必须unlock相同的次数,变量才能解锁。
  • 如果对一个变量执行lock操作,那么将会清空工作内存中次变量的值,在执行引擎使用这个变量之前,需要重新执行load或者assign操作初始化变量的值。
  • 如果一个变量事先没有被lock锁定,就不允许它执行unlock操作,也不允许unlock一个被其他线程锁定的变量。
  • 对一个变量unlock操作之前,必须先把此变量同步到主内存中。

此时,你想到了synchronized关键字一文的字节码了吗?

JMM如何保证三大特性

并发编程有3个非常重要的特性:原子性,有序性和可见性,我们在上一篇文章synchronized关键字中也有提到。

  • 原子性是指在一次的操作或者多次操作中,要么所有的操作全部得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。但是需要注意喔,两个原子的操作结合在一起不一定是原子的。
  • 可见性是指当一个线程对共享变量进行了修改,那么另外的线程可以立即看到最新值。
  • 有序性是指程序代码在执行过程中的先后顺序,这是由于Java编译器以及运行期间的优化导致了代码执行的顺序未必是开发者的编码顺序也即是指令重排序。当然指令指令的重排序严格遵守了指令之间的数据依赖关系,并不是可以任意重排序的。

JMM与原子性

在Java语言中,基本数据类型的变量读取赋值操作都是原子的,对引用类型的变量读取和赋值操作也是原子的。但是将一个变量的值赋值给另外一个变量的操作并不是原子的,JMM只保证了基本读取和赋值的原子操作,其他的均不保证。

x = 10;//原子操作
y = x;//非原子操作
x ++ ;//非原子操作
           

JMM与可见性

在多线程环境下,如果某个线程对变量执行了修改操作,则会将新值写入工作内存,再刷新至主内存,但是什么时候最新值刷新至主内存是不确定的。JMM提供了3种方式来保证可见性:volatile关键字,synchronized关键字,JUC包下的lock锁。

JMM与有序性

JMM同样提供了3种方式来保证有序性:volatile关键字,synchronized关键字,JUC包下的lock锁。此外JMM具备一些Happens-before规则能够保证有序性。

下一篇文章预告:《以volatile视角看JMM》

我是勾勾,一直在努力的程序媛,感谢您的点赞和转发!

参考资料:

《深入理解Java虚拟机》

《Java高并发编程详解》