JVM-运行时数据区
Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域,有着各自不同的作用。
缩略图
不同内存区域说明
PC寄存器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。
如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
VM栈
线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)
异常情况:
- StackOverflowError :线程请求的栈深度大于虚拟机所允许的深度
- OutOfMemoryError :如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存
本地方法栈
如果我们想再Java底层里调用别的语言代码的话就需要用到别的方法栈了,比如Java虚拟机的实现会用到传统的栈(C stack)来调用native方法,这个就是本地方法栈的应用,当然这个不是必须实现的,完全取决于虚拟机的实现。也会有 StackOverflowError 和 OutOfMemoryError 异常。
堆
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。
异常情况:
- OutOfMemoryError :如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。
方法区
在Java虚拟机中方法区是可提供各个线程共享的运行时内存区域,它存储了每一个类的结构信息,例如运行时常量池,字段和方法数据,构造函数和普通函数的字节码内容,一句话总结就是存储元数据地方。
运行时常量池是class文件中每个类或接口常量池表的表示形式。它包括了若干不同的常量,比如从编译期可知的数值字面量到运行时才能解析获得的方法或字段引用等等。
创建时机:
每个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口到虚拟机之后创建对应的运行时常量池。
补充说明——栈帧
局部变量表
每个栈帧内部都包含一组称为局部变量表的列表,变量表的长度在编译期决定。
一个局部变量可以存储一个基本数据类型或一个对象引用(referance),returnAddress的数据。存储long或double需要两个局部变量才能存储。
当虚拟机要使用局部变量表里的数据时通过索引来定位,默认从0开始,由于long和double占用两个局部变量所以它的索引较特殊,取决于最小的那个值,比如某个long类型数据在索引n和n+1里存储了,那么它对应的索引值就是n。虚拟机通过局部变量表来完成方法调用时的参数传递。如果是类方法,它的参数依次从0开始的位置传递到局部变量表,如果是实例方法则第0位置存储所在对象的引用(this),从1开始传递参数。
操作数栈
操作数栈是属于栈帧中的栈,其实它的全名叫做当前栈帧的初操作数栈。栈,栈帧,操作数栈的关系需要梳理清楚:
- 栈:是虚拟机运行时数据区的一个逻辑区域,里面存储了一个个栈帧。
- 栈帧:栈帧代表一个方法的整个生命周期,里头存储了局部变量表,操作数栈,动态链接
- 操作数栈: 刚刚创建时操作数栈是空的。虚拟机提供一些指令从局部变量表把一些常量或者变量值加载到操作数栈,也提供了从操作数栈取走数据的指令。调用方法时操作数栈用来准备调用方法的参数以及接受方法的返回结果。
动态链接
动态链接是用来完成运行时绑定操作的。在栈帧中有一个指向常量池的当前类的一个引用。在class文件里一个方法要是调用其他方法或者方法其他成员变量,则需要通过符号引用来表示。
- 动态链接的作用就是将符号引用转换为直接引用。
- 类加载的过程中将要解析尚未被解析的符号引用,并且把对变量的访问转换为正确的偏移量。