Java内存模型

由于计算机上的内存模型涉及到物理的主内存、高速缓存和寄存器等。这些不同的计算机不同的操场系统可能会存在差异,Java虚拟机规范中试图定义一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异,让Java程序在各个平台下都能达到一致的访问效果。

主内存与工作内存

Java内存模型规定了所有变量都存储在主内存内(主内存包括方法区和堆),此处主内存隶属于Java虚拟机内存的一部分,而虚拟机内存是操作系统分配的。每条Java线程还有自己的工作内存,工作内存中保存了被该线程使用到的变量的主内存的副本,线程对变量的所有操作都在工作内存中进行,Java线程之间的变量值传递都通过主内存来完成。

内存间的交互

关于主内存和工作内存间的交互协议,即一个变量如何从主内存拷贝到工作内存、又是如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了8种操作,这8种操作实现时必须保证每一种操作都是原子的、不可再分的,其中前4条是作用于主内存,后4条作用于工作内存:

  • lock锁定:将一个变量标识为线程独占状态
  • unlock解锁:将锁定状态的变量解除锁定,释放后的变量才可以被其他变量锁定
  • read读取:将变量从主内存传输到线程的工作内存中,待之后的load加载
  • write写入:把store操作从工作内存中得到的变量值写入主内存的变量中
  • load加载:将read后从主内存得到的变量值加载到工作内存的变量副本中
  • use使用:把工作内存中的一个变量值传递给字节码执行引擎,等待字节码指令使用
  • assign赋值:把一个从执行引擎接收到的值赋值给工作内存的变量
  • store存储:把工作内存中一个变量的值传送到主内存中,以便随后的write使用

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。其中程序计数器、JVM栈、本地方法栈是线程私有的,而方法区和堆是所有线程共享的。

运行时数据区域

程序计数器

一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器:如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行Native方法,这个计数器值为空。

Java虚拟机栈

Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表:局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量表所需的内存空间在编译期间完成分配。

动态链接:动态链接是在运行时将符号引用解析为直接引用的过程。

操作数:参与运算的常量或者变量称为操作数。

该区域可能抛出以下异常:

  • 当线程请求的栈深度超过最大值,会抛出StackOverflowError异常。
  • 栈进行动态扩展时如果无法申请到足够内存,会抛出OutOfMemoryError异常。

本地方法栈

本地方法栈与Java虚拟机栈类似,它们的区别只不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为本地方法服务。该区域可能抛出的异常与Java虚拟机栈一样。

Java堆

Java堆是Java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java堆是垃圾收集器管理的主要区域,还可以细分为新生代和老年代。

一般情况下,新创建的对象都会存放到新生代中。

在新生代每进行一次垃圾收集后,就会给存活的对象“加1岁”,当年龄达到一定数量的时候就会进入老年代,另外,比较大的对象也会进入老年代。

Java堆不需要物理上连续的内存空间,逻辑上连续即可。如果堆中没有内存完成实例分配且堆也无法再扩展时,将抛出OutOfMemoryError异常。

方法区

方法区也是各个线程共享的内存区域,之前是用永久代实现的,用于存储已被虚拟机加载的类信息、常量、静态变量等数据。由于永久代的回收效率低,对于永久代的大小指定困难且容易发生内存溢出等原因,JDK1.8彻底废弃永久代而使用元空间取代,并将字符串常量转移到堆中。元空间并不在虚拟机中,而是使用本地内存,因此默认情况下元空间的大小仅受本地内存限制。

控制参数汇总

可以通过如下参数来控制各区域的内存大小:

1
2
3
4
5
6
7
8
9
10
11
-Xms设置堆的最小空间大小

-Xmx设置堆的最大空间大小

-XX:NewRatio老年代/新生代

-XX:NewSize设置新生代最小空间大小

-XX:MaxNewSize设置新生代最大空间大小

-Xss设置每个线程的堆栈大小

参考资料

  • 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.