java内存模型

Java内存模型简称JMM(Java Memory Model),是Java虚拟机所定义的一种抽象规范,用来屏蔽不同硬件和操作系统的内存访问差异,让java程序在各种平台下都能达到一致的内存访问效果。以下是java内存模型的样子:


JMM中主内存和线程交互

1.主内存(Main Memory)

主内存可以简单理解为计算机当中的内存,但又不完全等同。主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊”。

2.工作内存(Working Memory)

工作内存可以简单理解为计算机当中的CPU高速缓存,但又不完全等同。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本”。

线程对共享变量的所有操作都必须在工作内存进行,不能直接读写主内存中的变量。不同线程之间也无法访问彼此的工作内存,变量值的传递只能通过主内存来进行。

还有一个概念JVM中的堆啊、栈啊、方法区什么的,是Java虚拟机的内存结构如下图:


很多人会将此和JMM搞混,这两个并不是同一个层次的划分,这两个基本上没有关系的,如果一定要勉强对应起来,那从变量,主内存,工作内存的定义来看,主内存对应java堆中的对象实例的数据部分,而工作内存则是对应虚拟机栈中的部分区域。

提问

介绍完概念,下面进入问答环节:

为什么要搞这么麻烦?所有线程直接操作主内存不行吗?

这里首先要先讲解下物理机中的并发问题,虚拟机的内存模型多半是参考物理机的并发处理方案。绝大多数的运算任务都不可能只靠处理器计算就能完成的,处理器至少要与内存交互,存储一些计算结果,这个I/O是很难消除的,但是由于计算机的存储设备与处理器的运算速度有几个数量级的差距。所以计算机不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存作为内存和处理器之间的缓冲,而每个处理器都有一个自己的高速缓存。内存模型也是同样的道理, 从更低层次来说,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的时工作内存。

如何保证工作内存的一致性?

每个线程都有一个自己的工作内存,而它们又共享同一个主内存。所以可能会导致各自的工作内存中的数据不一样。物理机中规定各个处理器访问缓存时都遵循一些协议,类似MSI,MESI,MOSI,SYNAPSE等等。而内存模型中则是靠CAS来保证的。具体的CAS这个章节先不细讲。

上面提到对于一个共享变量来说,工作内存当中存储了它的“副本”。这里是将整个对象都拷贝吗?这样会不会很占内存?

假设线程访问一个10mb的对象,是否会把这10mb的对象都复制出来呢?事实上并不是如此,这个对象的引用,对象中的某个在线程中访问到的字段有可能在拷贝,但不会一下把整个对象拷贝一次。毕竟存在堆中的对象,也是分成很多细小的部分存在不同的地方。


附录

Java虚拟机内存模型中定义了8种关于主内存和工作内存的交互协议操作:

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

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

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

load:作用于工作内存的变量,把read读取操作从主内存中得到的变量值放入工作内存的变量拷贝中。

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

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

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

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


Java内存模型对上述8种操作有如下的约束:

把一个变量从主内存复制到工作内存中必须顺序执行read读入操作和load载入操作。把一个变量从工作内存同步回主内存中必须顺序执行store存储操作和write写入操作。

read

和load操作之间、store和write操作之间可以插入其他指令,但是read和load操作、store和write操作必须要按顺序执行,即不允许read和load、store和write操作之一单独出现。

不允许一个线程丢弃它的最近的assign赋值操作,即工作内存变量值改变之后必须同步回主内存。只有发生过assign赋值操作的变量才需要从工作内存同步回主内存。

一个新变量只能在主内存中产生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,即一个变量在进行use和store操作之前,必须先执行过assgin和load操作。

一个变量在同一时刻只允许一条线程对其进行lock锁定操作,但是lock锁定可以被一条线程重复执行多次,多次执行lock之后,只有执行相同次数的unlock操作变量才会被解锁。

如果对一个变量执行lock锁定操作,将会清空工作内存中该变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

如果一个变量事先没有被lock锁定,则不允许对这个变量进行unlock解锁操作,也不允许对一个被别的线程锁定的变量进行unlock解锁。

一个变量进行unlock解锁操作之前,必须先把此变量同步回主内存中(执行store和write操作)。

当一个变量被声明为volatile之后,JMM对其做了特殊规则:

volatile变量的操作必须严格按load->use顺序,前一个动作是load时才能执行use动作,后一个动作是use时才能执行load动作,即每次在工作内存中使用变量前必须先从主内存中刷新最新的值,以保证能看到其他线程对变量的最新修改。

volatile变量的操作必须严格按assign->store顺序,前一个动作是assign时才能执行store动作,后一个动作是store时才能执行assign动作,即每次在工作内存为变量赋值之后必须将变量的值同步回主内存,以保证让其他线程能看到变量的最新修改。

若线程对volatile变量V的assign或者use操作先于对volatile变量W的assign或者use操作,则线程对volatile变量A的read/load或者store/write操作也必定先于对volatile变量B的read/load或者store/write操作。

volatile变量读操作的性能消耗与普通变量几乎没什么差别,但是写操作则可能慢一些,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生指令重排。

你可能感兴趣的:(java内存模型)