Java内存模型
2019-02-09
1 概述
衡量一个服务性能的高低好坏指标:
- TPS:每秒事务处理数,代表一秒内服务端平均能响应的请求总数。
2 硬件的效率与一致性
物理机遇到并发的处理方案:
-
增加高速缓存,作为内存和处理器之间的缓冲。将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存中,这样处理器无须等待缓慢的内存读写。
注意:存在缓存一致性问题。在多处理器系统中,每个处理器都有自己的高速缓存,而他们又共享一个主内存。通过规范缓存的读写协议来解决此问题。 -
乱序执行优化,为了使处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行优化。处理器会保证最终的结果是一致的,但不保证程序各个语句的计算顺序和输入顺序一致。
3 Java内存模型
Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
3.1 主内存和工作内存
Java内存模型的主要目标是定义程序中各个变量的访问规则,即虚拟机中将变量存储到内存和从内存中去除变量的底层细节。
注意:这里所说的变量指的是存储在JVM运行区域中线程共享区域的变量,如实例字段、静态字段和构成数组对象的元素。
- 所有的变量都存储在主内存中
- 每个线程还有自己的工作内存,工作内存存储在高速缓存或者寄存器中,保存了该线程使用到的变量的主内存副本拷贝。
- 线程只能直接操作工作内存中的变量,不能直接读写主内存。
- 不同线程之间也无法直接读取对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
主内存可以类比Java堆中的一部分,工作内存则可以类比虚拟机栈中的一部分。也可以将主内存看做物理内存,工作内存优先存储在高速缓存或者寄存器。
3.2 内存间交互操作
Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。虚拟机实现时必须保证每种操作都是原子的,不可再分的(对于long和double类型存在例外)。
- 作用于主内存:
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- 作用于工作内存:
- load(载入):在 read 之后执行,作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
- write(写入):在 store 之后执行,作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
- 如果要把一个变量从主内存复制到工作内存,需要顺序执行read和load操作
- 如果要把一个变量从工作内存同步回主内存,需要顺序执行store和write操作
注意: 上述两个操作必须顺序执行,但是不保证连续执行。也就是说,read和load之间、store和write之间是可以插入其他操作的。
Java内存模型中操作的执行原则:
- 不允许read和load、store和write操作之一单独出现。
- 不允许线程丢弃它的最近的assign操作,即变量在工作内存中改变之后,必须同步回主内存。
- 不允许线程把没有经过assign操作的变量,同步回主内存。
- 一个新的变量只能在主内存中诞生,即对一个变量进行use、store操作之前,必须先执行过load、assign操作。
- 一个变量在同一时刻只能被一条线程执行lock操作,一旦lock成功,可以被同一线程重复lock多次,多次执行lock之后,只有执行相同次数的unlock操作,变量才会被解锁。
- 对一个变量执行lock操作,将会清空工作内存中该变量的值,所以在执行引擎使用这个变量前,需要重新执行load或assign操作对其进行初始化。
- 对一个变量执行unlock操作之前,必须先把该变量同步回主内存(执行store、write操作)。
- 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许unlock一个被其他线程lock的变量。