虚拟机栈帧的结构
程序员文章站
2022-05-27 23:43:11
...
执行引擎是 java 虚拟机最核心的组成部分之一.在虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,这个概念模型称为各种虚拟机执行引擎的统一外观. 在不同的虚拟机实现里,执行引擎在执行 java 代码的时候,可能会解释执行和编译执行等,但是从外观上来看,所有的 java 虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.
运行时栈帧结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素.栈帧中存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息.
每个方法从调用开始到执行完成,对应着栈帧从入栈到出栈这么一个过程.
每一个栈帧都包括局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息. 在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都是一件完全确定的,并且写入到方法表的 Code 属性中,因此一个栈帧需要多大的空间,不受程序运行期间变量数据的影响.
一个线程中方法调用链可能会很长,很多方法都同时处于执行状态.对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法. 执行引擎运行的所有字节码指令都只针对当前栈帧进行操作,在概念模型上,典型的栈帧结构:
局部变量表:是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量. 在 java 程序被编译为 Class 文件时,就在方法的 Code 属性的 max_locals 数据项中确定了该方法所需分配的局部变量表的最大容量.
局部变量表的基本单位是 slot,虚拟机规范中并没有明确指明一个 Slot 应占用多大的内存空间大小,只是很有导向性地说到每个 Slot 都应该能存放一个 boolean、byte、char、short、int、float、reference 或 returnAddress 类型的数据.
为了尽可能的节省栈帧空间,局部变量表中的 Slot 是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器已经超过了某个变量的作用域,那么这个变量对应的 Slot 就可以交给其他变量使用. 但是这会有一个坏处,Slot 复用可能会影响到系统的垃圾收集行为.
局部变量和类变量不同,如果局部变量定义了但是没有赋初始值的话是不能使用的,不要认为 java 中任何情况下都存在诸如整型变量默认为 0,布尔变量默认为 false 这样的默认值.
操作数栈:同局部变量表一样,操作数栈的最大深度也在编译的时候写到了 Code 属性的 max_stacks 数据项中. 操作数栈的每个元素可以是任意的 java 数据类型,包括 long 和 double. 32 位数据类型所占的栈容量为 1,64 为数据类型所占的栈容量为 2. 在方法执行的任何时候,操作数栈的最大深度都不会超过在 max_stacks 数据项中设定的最大值.
当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作.
整数加法的字节码指令 iadd 在运行的时候操作数栈中最接近栈顶的两个元素已经放入了两个 int 型的数值,当执行这个指令时,会将这两个 int 值出栈并相加,然后将将相加结果入栈.
在概念模型中,两个栈帧是完全互相独立的,但是大多数虚拟机的实现里会做一些优化处理,令两个栈帧出现一部分重叠.
动态链接
每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用就是为了支持方法调用过程中的动态链接. 我们知道 Class 文件常量池中存在大量的符号引用,这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这个转化称为静态解析.另一部分将在每次运行期间转化为直接引用,这部分称为动态链接.
方法返回地址
当一个方法开始执行后,只有两种方式可以退出这个方法. 第一种是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者.另一种是方法在执行的过程中遇到了异常,并且这个异常没有在方法体内得到处理. 一个方法异常退出,是不会给它的上层调用者产生任何返回值的.
无论采用何种退出方式,在方法退出后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态. 一般来说,方法正常退出时,调用者的 PC 计数器的值可以作为返回地址,栈帧中很有可能会保存这个计数器值. 而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息.
方法退出时可能执行如下行为:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等.
附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现.
运行时栈帧结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素.栈帧中存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息.
每个方法从调用开始到执行完成,对应着栈帧从入栈到出栈这么一个过程.
每一个栈帧都包括局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息. 在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都是一件完全确定的,并且写入到方法表的 Code 属性中,因此一个栈帧需要多大的空间,不受程序运行期间变量数据的影响.
一个线程中方法调用链可能会很长,很多方法都同时处于执行状态.对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法. 执行引擎运行的所有字节码指令都只针对当前栈帧进行操作,在概念模型上,典型的栈帧结构:
局部变量表:是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量. 在 java 程序被编译为 Class 文件时,就在方法的 Code 属性的 max_locals 数据项中确定了该方法所需分配的局部变量表的最大容量.
局部变量表的基本单位是 slot,虚拟机规范中并没有明确指明一个 Slot 应占用多大的内存空间大小,只是很有导向性地说到每个 Slot 都应该能存放一个 boolean、byte、char、short、int、float、reference 或 returnAddress 类型的数据.
为了尽可能的节省栈帧空间,局部变量表中的 Slot 是可以重用的,方法体中定义的变量,其作用域并不一定会覆盖整个方法体,如果当前字节码PC计数器已经超过了某个变量的作用域,那么这个变量对应的 Slot 就可以交给其他变量使用. 但是这会有一个坏处,Slot 复用可能会影响到系统的垃圾收集行为.
局部变量和类变量不同,如果局部变量定义了但是没有赋初始值的话是不能使用的,不要认为 java 中任何情况下都存在诸如整型变量默认为 0,布尔变量默认为 false 这样的默认值.
操作数栈:同局部变量表一样,操作数栈的最大深度也在编译的时候写到了 Code 属性的 max_stacks 数据项中. 操作数栈的每个元素可以是任意的 java 数据类型,包括 long 和 double. 32 位数据类型所占的栈容量为 1,64 为数据类型所占的栈容量为 2. 在方法执行的任何时候,操作数栈的最大深度都不会超过在 max_stacks 数据项中设定的最大值.
当一个方法刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作.
整数加法的字节码指令 iadd 在运行的时候操作数栈中最接近栈顶的两个元素已经放入了两个 int 型的数值,当执行这个指令时,会将这两个 int 值出栈并相加,然后将将相加结果入栈.
在概念模型中,两个栈帧是完全互相独立的,但是大多数虚拟机的实现里会做一些优化处理,令两个栈帧出现一部分重叠.
动态链接
每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用就是为了支持方法调用过程中的动态链接. 我们知道 Class 文件常量池中存在大量的符号引用,这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这个转化称为静态解析.另一部分将在每次运行期间转化为直接引用,这部分称为动态链接.
方法返回地址
当一个方法开始执行后,只有两种方式可以退出这个方法. 第一种是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者.另一种是方法在执行的过程中遇到了异常,并且这个异常没有在方法体内得到处理. 一个方法异常退出,是不会给它的上层调用者产生任何返回值的.
无论采用何种退出方式,在方法退出后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态. 一般来说,方法正常退出时,调用者的 PC 计数器的值可以作为返回地址,栈帧中很有可能会保存这个计数器值. 而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息.
方法退出时可能执行如下行为:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等.
附加信息
虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息,这部分信息完全取决于具体的虚拟机实现.
上一篇: C++符号解析
下一篇: 虚拟机字节码执行过程