Java内存模型
简述
Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
定义模型的目标
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。
这里说的变量包括实例字段、静态字段和构成数组对象的元素,不包括局部变量与方法参数,因为后者是线程私有的,不会共享,也就不存在竞争的问题。
Java 面向对象编程 P96
每当用java命令启动一个java虚拟机进程时,Java虚拟机就会创建一个主线程,该线程从程序入口main()方法开始执行。主线程在Java栈区内有一个方法调用栈,每执行一个方法,就会向方法调用栈中压入一个包含该方法的局部变量以及参数的栈帧、
主线程首先把main()方法的栈帧压入方法调用栈,在这个栈帧中包含四个param局部变量 ,当主线程开始执行changeParameter()方法时,会把该方法的栈帧也压入方法调用栈。在这个栈帧中包含四个param参数,它们的初始值由main()方法的四个param局部变量传递。
图3-15显示了主线程开始准备执行changeParameter()方法时方法调用栈的状态。
查看代码
/** * P96 例程 3-5 ParamTester.java*/ public class ParamTester { public int memberVariable = 0; //成员变量 public static void main(String[] args) { //声明并初始化4个局部变量 int param1 = 0; //param1是基本数据类型 ParamTester param2 = new ParamTester(); //param2是引用对象类型 ParamTester param3 = new ParamTester(); //param3是引用对象类型 int [] param4 = {0}; //param4是数组引用类型 //将四个局部变量作为参数传递给changeParameter()方法 changeParameter(param1,param2,param3,param4); //打印四个局部变量 System.out.println("param1="+param1); System.out.println("param2.memberVariable="+param2.memberVariable); System.out.println("param3.memberVariable="+param3.memberVariable); System.out.println("param4[0]="+param4[0]); } public static void changeParameter(int param1,ParamTester param2,ParamTester param3,int[] param4) { param1 = 1; //改变基本数据类型参数的值 param2.memberVariable = 1; //改变对象类型参数的实例变量 param3 = new ParamTester(); //改变对象类型参数的引用,使它引用一个新对象 param3.memberVariable = 1 ; //改变新的对象的实例变量 param4[0] = 1; //改变数组类型参数的元素 } }
13.1 Java线程的运行机制 P365
在JAVA虚拟机中,执行程序代码的任务主要是由线程来完成的。每个线程都有一个独立的程序计数器和方法调用栈.
* 程序计数器:也成为PC寄存器,当线程执行一个方法时,程序计数器指向方法中下一条要执行的字节码指令。* 方法调用栈: 简称方法栈,用来跟踪线程中一系列的方法调用过程,栈中的元素称为栈帧。每当程序调用一个方法的时候,就会向方法栈压入一个新帧。帧用来存储方法的参数,局部变量和运算过程中的临时数据。
帧由以下三个部分组成:
* 局部变量区: 存放局部变量和局部参数。
* 操作数栈: 是线程的工作区,用来存放运算过程中生成的临时数据。
* 栈数据区: 为线程执行指令提供相关的信息,包括如何定位到堆区和方法区的特定数据,以及如何正常退出方法或者异常中断方法。每当用java命令启动一个Java虚拟机进程时,Java虚拟机都会创建一个主线程,该线程从程序入口main()方法开始执行。
查看代码
下面以例程13-1的Sample为例,介绍线程的运行过程。
//P365 例程 13-1 Sample.java public class Sample { private int a; //实例变量 public int method() { int b = 0; //局部变量 a++; b=a; System.out.println("this.a "+this.a); return b; } public static void main(String[] args) { Sample s = null; //局部变量 int a = 0; //局部变量 s = new Sample(); a=s.method(); System.out.println(a); } }
主内存与工作内存
Java内存模型规定了所有的变量都存储在主内存(Main Memory)中,此外每条线程还有自己的工作内存(Working Memory)。
线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存中的变量。
并且,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值得传递均需要通过主内存来完成,线程、主内存、工作内存关系如下图:
也可以把这里的主内存与工作内存概念与JVM运行时数据区进行对应,主内存主要对应Java堆中的对象实例数据部分,工作内存对应于虚拟机栈中的部分区域。
内存间的交互动作
动作 | 作用 |
lock (锁定) |
作用于主内存变量,把一个变量标示为一条线程独占的状态 |
unlock (解锁) |
作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 |
read (读取) |
作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用 |
load (载入) |
作用于工作内存的变量,把read操作从主存中得到的变量值放入工作内存的变量副本中 |
use (使用) |
作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作 |
assign (赋值) |
作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作 |
store (存储) |
作用于工作内存的变量,把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用 |
write (写入) |
作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中 |