JVM (java虚拟机)JAVA虚拟栈(线程私有)
线程私有内存空间,它的生命周期和线程相同。线程执行期间,每个方法被执行时,都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法从被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。「属于线程私有的内存区域」
栈帧(Stack Frame)
栈帧(Stack Frame)是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack) 的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
在编译期,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中 ,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与这个栈帧相关联的方法称为当前方法(Current Method)。典型的栈帧结构如图:
局部变量表
(个人理解 用于存放方法参数和方法内部定义的局部变量
)
局部变量表局部变量表是 Java 虚拟机栈的一部分,是一组变量值的存储空间,用于存储方法参数和局部变量。 在 Class文件的方法表的 Code 属性的 max_locals 指定了该方法所需局部变量表的最大容量。
局部变量表在编译期间分配内存空间,可以存放编译期的各种变量类型:
基本数据类型 :boolean, byte, char, short, int, float, long, double等8种;
对象引用类型 :reference,指向对象起始地址的引用指针;不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置
返回地址类型 :returnAddress,返回地址的类型。指向了一条字节码指令的地址
变量槽(Variable Slot):
变量槽是局部变量表的最小单位,规定大小为32位。对于64位的long和double变量而言,虚拟机会为其分配两个连续的Slot空间。
操作数栈
(个人理解 操作数栈就是栈中数据通过进栈出栈的方法操作栈中的数据)
操作数栈(Operand Stack)也常称为操作栈,是一个后入先出栈。在 Class 文件的 Code 属性的 max_stacks 指定了执行过程中最大的栈深度。Java虚拟机的解释执行引擎被称为基于栈的执行引擎 ,其中所指的栈就是指-操作数栈。
和局部变量表一样,操作数栈也是一个以32字长为单位的数组。
虚拟机在操作数栈中可存储的数据类型:int、long、float、double、reference和returnType等类型 (对于byte、short以及char类型的值在压入到操作数栈之前,也会被转换为int)。
和局部变量表不同的是,它不是通过索引来访问,而是通过标准的栈操作 — 压栈和出栈来访问。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。
虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈。
begin
iload_0 // push the int in local variable 0 onto the stack
iload_1 // push the int in local variable 1 onto the stack
iadd // pop two ints, add them, push result
istore_2 // pop int, store into local variable 2
end
在这个字节码序列里,前两个指令 iload_0 和 iload_1 将存储在局部变量表中索引为0和1的整数压入操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并把它存储到局部变量表索引为2的位置。
下图详细表述了这个过程中局部变量表和操作数栈的状态变化(图中没有使用的局部变量表和操作数栈区域以空白表示)。
动态链接
每个栈帧都包含一个指向运行时常量池中所属的方法引用,持有这个引用是为了支持方法调用过程中的动态链接。
Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用:
静态解析:一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,
动态解析:另一部分将在每一次的运行期间转化为直接引用,称为动态链接。
方法返回地址
当一个方法开始执行以后,只有两种方法可以退出当前方法:
正常返回:当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion),一般来说,调用者的PC计数器可以作为返回地址。
异常返回:当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion),返回地址要通过异常处理器表来确定。
当一个方法返回时,可能依次进行以下3个操作:
恢复上层方法的局部变量表和操作数栈。
把返回值压入调用者栈帧的操作数栈。
将PC计数器的值指向下一条方法指令位置。
小结
注意:在Java虚拟机规范中,对这个区域规定了两种异常。
其一:如果当前线程请求的栈深度大于虚拟机栈所允许的深度,将会抛出 StackOverflowError 异常(在虚拟机栈不允许动态扩展的情况下);
其二:如果扩展时无法申请到足够的内存空间,就会抛出 OutOfMemoryError 异常。