深入理解JAVA虚拟机学习笔记26——字节码执行引擎(栈帧)
每天进步一点点!
今天主要了解一下栈帧中的内容,大部分为内容都比较容易理解,且做以下记录。
首先介绍以下栈帧的概念:
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。
栈帧中主要存储了5方面的内容:方法的局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。
在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。
一个线程中的方法调用链可能会很长,很多方法都同时处于执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current
Stack Frame),这个栈帧所关联的方法称为当前方法(Current
Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。
栈帧的概念结构如下图所示:
下面让我们来简单介绍以下,栈帧中的五个方面。
1. 局部变量表:是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。
在方法的Code属性的max_locals数据项中,确定了该方法所需要分配的局部变量表的最大容量。
局部变量表的容量以变量槽(Variable Slot,下称Slot)为最小单位,slot的长度随处理器,操作系统和虚拟机的不同而变化。
注意:Slot是可以重用的,当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。
对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的Slot空间。
虚拟机的引用需要做到两点:
一是从此引用中直接或间接地查找到对象在Java堆中的数据存放的起始地址索引;
二是此引用中直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息。
局部变量表建立在线程的堆栈上,是线程私有的数据,无论读写两个连续的Slot是否为原子操作,都不会引起数据安全问题。
局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。
如果是64位数据类型的变量,会同时使用n和n+1两个Slot。
如果执行的是实例方法(非static的方法),那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用。
局部变量不像前面介绍的类变量那样存在“准备阶段”,一次在准备阶段,赋予系统初始值;另外一次在初始化阶段,赋予程序员定义的初始值。
2. 操作数栈:操作数栈的最大深度也在编译的时候写入到Code属性的max_stacks数据项中。
操作数栈的每一个元素可以是任意的Java数据类型,32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
这里有一点需要注意的是,操作数栈中元素的数据类型必须与字节码指令的序列严格匹配。
为了对栈帧进行优化,虚拟机会让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调用时就可以共用一部分数据。
3. 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。
这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。这块不知道大家对前面说的静态绑定和绑定有没有印象,其实道理都是差不多的。
4. 方法返回地址:当一个方法开始执行后,只有两种方式可以退出这个方法。第一种方式是执行引擎遇到任意一个方法返回的字节码指令,如return,这种退出方法的方式称为正常完成出口(Normal Method Invocation Completion)。
另外一种退出方式是,在方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为异常完成出口(Abrupt
Method Invocation Completion)。
无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行;而方法异常退出时,返回地址是要通过异常处理器表来确定的。
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
5. 附加信息:虚拟机规范允许具体的虚拟机实现增加一些规范里没有描述的信息到栈帧之中。
以上即为栈帧的知识点,都是比较概念化的东西,大部分摘自《深入理解Java虚拟机》,这里权且记录,侵删。
喜欢文章或想一起学习的朋友可以关注我,给我点赞,我将会持续更新,有什么疑问或文中有不当之处请给我留言,真诚地希望能与大家一起交流探讨,学习进步!