Java运行时栈帧结构
Java运行时栈帧结构
Java虚拟机以方法作为最基本的执行单元,“栈帧”则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈的栈元素,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址和一些额外的附加信息。在编译Java程序源码的时候,栈帧中需要多大的局部变量表,需要多深的操作数栈就已经被分析计算出来了,并且写到方法表的code属性中。换言之,一个栈帧需要分配多少内存,并不会受到运行时变量的数据的影响,仅仅取决于程序源码和具体的虚拟机实现的栈内存布局形式。
以Java程序的角度来看,同一时刻、同一条线程里面,在调用堆栈的所有方法都同时处于执行状态。而对于执行引擎来讲,在活动线程里面,只有位于栈顶的方法才是运行的,只有位于栈顶的栈帧才是生效的,其被称为“当前栈帧”,与这个栈帧所关联的方法称为“当前方法”。
一、局部变量表
局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序被编译为class文件时。就在方法的code属性的max_locals数据项中确定了该方法所需分配的局部变量表的最大容量。Java虚拟机规范没有明确指出一个变量槽的大小,但是每个变量槽都应该能存放一个Boolean、byte、char、short、int、float、reference或returnAddress类型的数据,这8种数据类型,都可以使用32位或更小的物理内存来存储。对于64位的数据类型,Java虚拟机会以高位对齐的方式为其分配俩个连续的变量槽空间,Java语言中明确64位的数据类型只有long和double俩种。
Java虚拟机通过索引定位的方式使用局部变量表,索引值的范围时从0开始至局部变量表最大的变量槽数。如果访问的是32位数据类型的变量,索引N就代表了使用第N个变量槽,如果访问的是64位数据类型的变量,则说明会同时使用第N和N+1俩个变量槽。
当一个方法被调用,Java虚拟机会使用局部变量表完成参数值到参数变量列表的传递过程,如果执行的是实例方法,那局部变量表中第0位索引的变量槽默认是实例引用(可通过this使用),其余参数按照参数表顺序排列。为了尽可能节省栈帧耗用的内存空间,局部变量表的变量槽是可以复用的。
二、操作数栈
操作数栈也常被称为操作栈,它是一个后入先出栈。同局部变量表一样,操作数栈的最大深度也在编译阶段被写入到code属性的max_stacks数据项中。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
当一个方法体刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,就是出栈和入栈的操作。在概念模型中,俩个不同的栈帧作为不同方法的虚拟机栈的元素,是完成相互独立的。但是在大多的虚拟机栈中会进行一些优化处理,令俩个栈帧出现一部分重叠。
三、动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。calss文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数,这些符号引用会在类加载阶段或者第一次使用的时候转化为直接引用,这个转化称为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分就称为动态连接。
四、方法返回地址
当一个方法开始执行后,只有俩种方式退出这个方法。第一种方式是执行引擎遇到任意一个方法返回字节码指令,这个时候可能会有返回值传递给上层的方法调用者,这个方法是否有返回值以及返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为“正常调用完成”
另一种退出方式是在方法执行中遇到了异常,并且这个异常没有在方法体中得到妥善的处理,无论以何种方式产生异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出。这种退出方法的方式称为“异常调用完成”。此时是不会给它的上层调用者提供任何返回值的。
在方法退出后,都必须返回到最初方法被调用时的位置,程序才能继续执行。一般来说,方法正常退出,主调方法的pc计数器就可以作为返回地址,栈帧中很可能会保存这个计数器的值。方法异常退出时,返回地址是通过异常处理表来确定的。
五、附件信息
这里的附加信息是Java虚拟机规范虚拟机实现可以增加一些其他信息到栈帧中,例如与调试、性能收集相关的信息,这部分信息完全取决于具体的虚拟机实现,一般会把动态连接、方法返回地址与附加信息全部归为一类,称为栈帧信息。
参考:
《深入理解Java虚拟机》周志明