Java并发编程-Java内存模型(JMM)与Java虚拟机(JVM)

194 阅读5分钟

Java内存模型(Java Memory Model,JMM)是一种Java虚拟机规范,与我们经常说到的JVM内存结构不是一个层次的概念。

一、Java内存模型

Java内存模型是一种Java虚拟机规范,用于屏蔽Java程序在不同硬件和操作系统访问内存的差异,保持内存的一致性。Java内存模型主要保证共享内存部分的原子性、可见性和有序性,Java内存模型定义了共享内存中多线程读写行为的规范。

  • Java内存模型中线程共享变量:实例域、静态域、数组元素,存储在堆内存中;
  • 线程私有变量:局部变量、方法定义参数、异常处理参数。

Java内存模型的抽象结构:

工作内存与主内存交互

Java内存中线程的工作内存和主内存的交互是由Java虚拟机定义了如下的8种操作来完成的,每种操作必须是原子性的(double和long类型在某些平台有例外)。

  • lock(锁定): 作用于主内存的变量,一个变量在同一时间只能一个线程锁定;
  • unlock(解锁): 作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放;
  • read(读取): 作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用;
  • load(载入): 作用于线程工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中;
  • use(使用): 作用于线程工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作;
  • assign(赋值): 作用于线程工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作;
  • store(存储): 作用于线程工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用;
  • write(写入): 作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

在执行这8中操作的时候必须遵循如下的规则:

  • 不允许read和load、store和write操作之一单独出现
  • 不允许一个线程丢弃最近的assign操作
  • 不允许一个线程回写没有修改的变量到主内存
  • 变量只能在主内存中产生
  • 一个变量在同一时刻只能被一个线程对其进行lock操作
  • 对变量执行lock操作,就会清空工作空间该变量的值
  • 不允许对没有lock的变量执行unlock操作
  • 对一个变量执行unlock之前,必须先把变量同步回主内存中

volatile修饰的变量的特殊规则

  • volatile类型的变量保证对所有线程的可见性。
  • volatile类型的变量禁止指令重排序优化。

如何保证原子性、可见性和有序性

原子性

在Java中提供了两个高级的字节码指令monitorenter和monitorexit,在Java中对应的Synchronized来保证代码块内的操作是原子的。

可见性

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变 量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

有序性

在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

二、JVM内存结构

Java虚拟机在执行Java程序过程中,把所管理的内存划分为若干个不同的数据区域,这些区域有着各自的用途,以及创建和销毁时间。JVM内存结构由方法区、堆、虚拟机栈、程序计数器、本地方法栈组成。

  • 方法区(Mehtod Area): 共享内存区,存储已被JVM加载的类信息、常量、静态变量、即时编译后的代码等。其中的Runtime Constant Pool(运行时常量池), 用于存放编译器生成的符号引用和字面量。
  • Java堆(Heap): 共享内存区,虚拟机启动时创建,占用区域最大,存放对象实例,是GC机制管理的主要区域,所以也被叫做GC堆。
  • 虚拟机栈(JVM Stacks): 线程私有内存区域,与线程同时创建,总数与线程数关联,代表Java方法执行的内存模型。
  • 程序计数器(Program Counter Register): 线程私有内存区域,占一小块内存区域,用于指示当前执行字节码的行号,是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
  • 本地方法栈(Native Method Stacks): 线程私有内存区域,本地方法栈与虚拟机栈发挥的功能类似,只是虚拟机栈为虚拟机执行Java方法而服务,而本地方法栈为虚拟机执行native方法而服务。