运行时数据区

Java 虚拟机在会将它所管理的内存划分成若干个不同的区域,作用各不相同。

线程私有区

PC寄存器

PC 寄存器是一块较小的内存区域,属于线程私有。PC 寄存器用于保存当前线程中正在执行的字节码指令的地址:对于非原生方法,指向的是字节码指令的地址;对于原生方法,保存的是 undefined。

Java虚拟机栈

Java 虚拟机栈也是属于线程私有,随线程同时创建,用于存储栈帧(Frame)。其内存既可以设置成固定大小,也可以根据计算动态扩展和收缩;使用的内存不需要保证是连续的。

Java 虚拟机栈可能会抛出以下异常:

  • StackOverflowError:线程请求分配的栈容量超过栈允许的最大值;
  • OutOfMemoryError: 栈尝试扩展但无法申请到足够的内存或者创建新的线程时没有足够的内存;

本地方法栈

本地方法栈与 Java 虚拟机栈的作用类似,主要用于支持 native 。如果虚拟机支持本地方法栈,在线程创建时按线程分配。

同 Java 虚拟机栈一样,本地方法栈有可能会抛出下异常:

  • StackOverflowError:线程请求分配的栈容量超过栈允许的最大值;
  • OutOfMemoryError: 栈尝试扩展但无法申请到足够的内存或者创建新的线程时没有足够的内存;

HotSpot 虚拟机直接把本地方法栈和 Java 虚拟机栈合二为一。

栈帧

栈帧是用来存储数据和部分过程结果的数据结构,
同时也用来处理动态链接、方法返回和异常分发。栈帧在线程内随着方法调用而创建、方法结束(包含抛出异常)而销毁。其存储空间由创建它的线程在虚拟机栈中分配。

每个栈帧都有自己的本地变量表、操作数栈和指向当前方法所属类的运行时常量池的引用,本地变量表和操作数栈的容量在编译期确定。

本地变量表

本地变量表用于保存局部变量,位于栈帧中,长度由编译期决定。

在本地变量表中保存 boolean、byte、char、short、int、float、reference或returnAddress 类型的数据需一个局部变量;保存 long、double 类型的数据需两个连续的局部变量。在本地变量表中的变量使用索引来定位访问,第一个变量的索引为0;在实例方法中,本地变量表中索引为0的位置存储的是该实例方法所在对象的引用(this)。

操作数栈

操作数栈是一个后进先出的栈,位于栈帧中,最大深度由编译期决定。

在任意时刻,操作数栈都有一个确定的深度,long、double类型会占用两个单位的深度,其他数据类型只占一个单位的深度。

动态链接

动态链接可以理解为栈帧内部指向当前方法所在类型的运行时常量池的引用。

线程共享区

Java堆

Java 堆是供所有类实例和数组对象分配内存的共享区域。它在虚拟机启动时被创建,存储被自动内存管理系统(垃圾收集器)所管理的各种对象。Java 堆的容量既可以设置成固定大小,也可以根据计算动态扩展和收缩;使用的内存不需要保证是连续的。

当实际所需的堆超出了自动内存管理系统能提供的最大值,会抛出OutOfMemoryError异常。

方法区

方法区存储了每一个类的结构信息:运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容;还包含一些在类、实例、接口初始化时用到的特殊方法。方法区是堆的逻辑组成部分,但虚拟机可以在这个区域不实现垃圾回收和压缩。方法区的容量既可以设置成固定大小,也可以根据计算动态扩展和收缩;使用的内存不需要保证是连续的。

当方法区的内存不能满足内存分配请求,会抛出OutOfMemoryError异常。

运行时常量池

运行时常量池是class文件中每一个类或接口的常量池表的运行时表现形式。在方法区中分配,在加载类或接口到虚拟机后会被创建。

当创建接口或类构造运行时常量池所需的内存超过了方法区所能提供的最大值时,会抛出OutOfMemoryError异常。

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范字定义的内存区域。
在 JDK 1.4中新加入的 NIO 引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆 和 Native 堆中来回复制数据。

当动态扩展直接内存的大小导致各个内存区域总和大于本机的物理内存限制时,会抛出OutOfMemoryError异常。

思维导图