JVM (3) -JVM Memory Model Part A: Main Memory and Working Memory and their communication

Java Memory Model (JMM) Overview

所谓JMM即可粗略理解为 “JVM中为针对变量在内存中写入写出而制定的一系列规则”, 也即以线程安全为主要目标的一系列底层基础设计。其存在目的即为: “使程序再各种不同架构的系统中都可保证并发安全, 即保证原子性,可见性,有序性

该篇文章涵盖以下内容:

  • JVM中线程的通信
  • JVM中工作内存与主内存交互的8个基本操作
  • JVM中针对上述八个基本操作而指定的几条基本规则
  • 从JMM角度解释线程安全问题的根本原因
  • JVM中的原子性, 可见性及有序性

JVM中线程的通信

JVM中的内存被分为工作内存主内存, 其中主内存为所有线程共享, 也即主内存中的内容对于所有线程均是可见且可访问的, 而工作内存则为线程私有。 每条线程均拥有自己的工作内存, 且线程间不可访问对方的工作内存。

在JVM中, 所有变量均存储在主内存中, 工作内存若需使用某个变量,则需从主内存中拷贝一份变量副本, 然后对副本进行操作, 最后写回主内存。 注意, 此处的变量特指线程共享的变量, 包括 instance’s fields, static fields, and elements of array, 其不包括线程私有的变量,即局部变量和方法参数等。

由于线程间不能互相访问对方的工作内存, 那么为了实现线程间的通信,就只能通过一个“第三方” — 主内存。其具体方式为: “若线程A与线程B间想要实现通信, 则A需先将变量值写回主内存, 之后线程B从主内存中复制该值, 进而实现通信目的”

工作内存&主内存 VS 堆&栈

至此, 你可能会认为主内存即对应着JVM中的堆, 而工作内存则对应着JVM中的栈。 然而, 实际上工作内存/主内存 与堆栈并非同一Scope下的概念。 如果一定要将两个概念向类比, 那么工作内存可对应位堆中的实例数据部分, 而工作内存可对应为栈中的部分数据

首先, 之所以说两者属于不同的Scope, 是因为主内存对应于物理内存, 工作内存则会有限被存储于寄存器和缓存中(因为程序运行时主要访问的是工作内存, 而将工作内存放置于寄存器和缓存可提高运行速度)。

同时,堆中不仅仅是实例数据,栈中也不仅仅是局部变量。 上文提到工作内存的作用为存放线程共享变量, 但尽管成员变量,类变量,数组元素等共享变量是存储与堆中, 这些变量仅仅对应着堆中的实例数据的部分,而非全部。 因为堆中除了存储实例数据以外, 还包含着Object Head and padding。具体来讲, 当一个对象被new到堆中以后, 其在堆中的结构由三部分组成: Object Head + 实例数据 + padding。 其中Object Head 中存放着 Mark Word + 类型指针。Mark Word作用为存储 锁状态, GC年龄等, 类型指针作用为指明该实例所属的是哪个类。

JVM中工作内存与主内存交互的8个基本操作

交互所需的8个基本操作即在实现工作内存与主内存的交互过程中所需要的八个最基本操作。 也即不论你要进行何种复杂的操作, 其皆为这八个基本操作中的多个或全部组成。

八个基本操作如下:

  • lock: 对主内存中某一变量上锁,使其只可被一条线程独占。 同一时间只有一条线程可对一个变量lock。
  • unlock: 解锁, 且解锁后才可被其他线程Lock。
  • read: 作用于主内存变量,将该变量的值传输到工作内存中, 之后才可进行load。 即主内存中的值传入工作内存中的过程。 仅指“传输”
  • load: 作用于工作内存,将read进来的变量放入工作内存的副本。 即将“传输”进来的变量值, 更新到工作内存中的过程。 仅指“更新/对工作内存的内存赋值”
  • use: 于工作内存,将工作内存中变量的值交给执行引擎进行操作。
  • assign: 于工作内存,接受工作引擎操作后的值,然后将其赋值回工作内存。 注意,此处可以发现工作引擎与工作内存间的交互过程不同于工作内存与主内存间的交互过程。 后两者交互过程中“传输”和“更新”是分开的, 而前两者则是一体的。
  • store: 作用于工作内存,把工作内存中的变量值发给主内存。即工作内存中的值传入主内存的过程。 仅指“传输”
  • write: 作用于主内存变量,将store进来的值更新到主内存。工作内存中的值传入主内存以后, 工作内存更新内存中的值的过程。 仅指“更新”

其中, “store/write and read/load 操作之间一定是顺序执行,但不一定是连续执行,即store and write之间, read and load之间,是可以插入其他指令的”。 这就是线程不安全的根源所在。

同时, 这八个操作都为原子性,但是在某些架构的系统中,对于long/double的读写可能不具有原子性,因为是64位占两个格子。

JVM中工作内存与主内存交互的几条基本规则

在执行上述八个操作时, 必须满足下列规则:

  1. read & load 以及 store & write 必须不可单独出现,及read 必load, store必write;即, 如果工作内存从主内存中复制来一个值, 则必须在工作内存中写入这个值。同理, 当主内存从工作内存中接受了一个值, 也必须在主内存中更新该值。
  2. 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  3. 没有assign操作则不可将变量更新回主内存。 也即, 若想将一个变量值写回主内存, 则其前提是这个变量值已经被工作引擎使用完且已经写回了工作内存。
  4. 只有主内存可生成新的变量,不可由工作内存自己生成新的变量, 即use, store 之前必有assign 和load。
  5. 一个变量同一时间只可被一条thread lock, but can be locked many times。若被上锁多次, 则也许被解锁同样次数。
  6. 当执行lock操作后,工作内存中该变量的值将被清空,在use之前需重新进行Load 或 assign操作以”初始化”(this is why assign is also OK)该变量的值。即在线程对于一个变量上锁后, 必定要重新read&load该变量的值。
  7. 只可由lock的线程进行unlock, 且one var can only be locked by one thread。
  8. unlock之前,必须将变量值更新回主内存。

此八个规则的设定目的即在于保证并发安全。对于这八个规则, 只需记住:“这八个规则的存在, 以及volatile的特殊规则,共同保证了JVM中的并发安全”

JVM中的原子性, 可见性及有序性

可见性

DEF:

即一个变量值被修改后,其他线程可立即见到这个值。

JVM中的保证措施:

通过 “在使用前刷新值,使用后写回主内存“ 保证, no matter it is volatile variable or ordinary one。 两这区别在于 volatile 随时可见,普通变量写回后可见。

volatile, synchronized, final均可保证可见性。

synchronized 可保证是因为 synchronized 在底层实现其实就是 lock and unlock, 而上述规则中规定, JVM内存模型规定进行 unlock操作必须进行store and write 操作, 也即必须将值更新回主内存后才可unlock

而final关键字的可见性是指:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程中就能看见final字段的值

这里可体现 synchronized and volatile 一个区别, volatile相当于系统自动的, 在变量值更新后自动写回主内存,且在某线程要使用volatile变量时自动刷新,,进而可随时保证可见性。 而 synchronized 只有在unlock后在能保证了可见性

原子性

DEF:

原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败。及时在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

JVM中的保证措施:

由Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个。我们大致可以认为,基本数据类型的访问、读写都是具备原子性。 例如, a=10这个操作是原子性的, 而a++ 则不是。

有序性

有序性指: 如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内似表现为串行的语义”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。

JVM中的保证措施:

  1. 防止指令重排 – volatile关键字
  2. 同一时间内一个变量只可被一条线程lock, 也即保证一时间内只有一个线程可操作某个变量 (这是因为 其保证了持有同一个锁的两个同步块只可串行的进入) – synchronized 关键字

Created by Shain at 2021/12/4, Melbourne, Australia.