1.java虚拟机栈
- java虚拟机栈是线程私有的, 生命周期与线程相同。
- 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlow异常;
如果虚拟机栈可以动态扩展,但是扩展时无法申请到足够的内存,就会抛出OutOfMemeryError异常; - java虚拟机栈描述的是java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。
2.栈帧(Stack Frame)
- 栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的java虚拟机栈的栈元素。
- 栈帧用于存储一个方法的局部变量表、操作数栈、动态链接、方法出口等信息。
每个方法被调用直至执行完成的过程对应一个栈桢在虚拟机中从入栈到出栈的过程。
注意:
在编译程序代码的时候,栈帧中需要多大的局部变量表内存,多深的操作数栈都已经完全确定了。
因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
栈结构图:
注意
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。
执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。
3.局部变量表
- 局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
并且,在Java编译为Class文件时,就已经确定了该方法所需要分配的局部变量表的最大容量。 - 局部变量表存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double「String是引用类型」),对象引用(reference类型)和returnAddress类型(它指向了一条字节码指令的地址)。
关于returnAddress:
在 JVM 中,数据分为两大类:primitive types (原生类型)和 reference types(引用类型)。
原生数据类型包括:numeric types, boolean type, returnAddress type。其中 returnAddress 数据只存在于字节码层面,与编程语言无关,也就是说,我们在 Java 语言中是不会直接与 returnAddress 类型的数据打交道的。
returnAddress 类型的值是指向字节码的指针,不管是物理机还是虚拟机,运行时内存中的数据总归可分为两类:代码,数据。对于冯诺依曼结构的计算机,指令数据和数值数据都存储在内存中,而哈弗结构的计算机,将程序指令与数据分开存储。
对于 JVM 来说,程序就是存储在方法区的字节码指令,而 returnAddress 类型的值就是指向特定指令内存地址的指针。
JVM支持多线程,每个线程有自己的程序计数器(pc register),而 pc 中的值就是当前指令所在的内存地址,即 returnAddress 类型的数据,当线程执行 native 方法时,pc 中的值为 undefined。
注意
"基本数据和对象引用存储在栈中。"这种说法只针对局部变量。如果是成员变量,或者定义在方法外对象的引用,它们存储在堆中。
4. 变量槽(Variable Slot)
- 局部变量表的容量以变量槽为最小单位,每个变量槽都可以存储32位长度的内存空间,例如boolean、byte、char、short、int、float、reference。
- 对于64位长度的数据类型(long、double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。
5.reference(对象实例的引用)
具体参见链接https://blog.csdn.net/wangdong5678999/article/details/68931538/
6. 动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所述方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也成为静态解析;
另外的一部分将在每一次运行时期转化为直接引用,这部分成为动态链接。
方法区里静态解析式不完全正确,要在运行期间栈帧进栈的时候动态连接到真实的类和方法。
参考: 栈帧中动态链接的理解
7. 方法出口
当一个方法开始执行后,只有两种方式可以推出这个方法:
方法返回指令:执行引擎遇到一个方法返回的字节码指令,可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口;
异常退出: 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致退出。
无论采用哪种退出方式,方法推出后都需要返回到方法被调用的位置程序才能继续执行,方法返回是可能需要在栈帧中保存一些信息。
一般来说,正常退出时,调用者的PC计数器的值可以作为返回地址(returnAddress type),栈帧会保存这个计数器值。
然而方法异常退出时,返回地址需要通过异常处理器来确定,栈帧中一般不会保存这部分信息。