运行时栈帧结构
栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储类方法的局部变量表、操作数栈、动态链接的方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中,所以一个栈帧需要分配多少内存,不会收到程序运行期间变量的数据影响。
只有位于栈顶的栈帧才是有效的,称为当前栈帧(current Stack Frame),与这个栈帧相关联的方法称为当前方法(current method)
局部变量表
局部变量表建立在线程的堆栈上,是线程私有数据
局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了该方法所需要分配的局部变量表的最大容量。
局部变量表的容量以变量槽Slot为最小单位,每个Slot都能存放一个boolean、byte、char、short、float、int、reference、或returnAddress类型的数据,这8中数据类型都可以用32位或更小的物理内存在存放。
reference类型表述对一个对象实例的引用,虚拟机实现这个引用应该做到两点,
- 是从此引用中直接或简介地查找到对象在Java堆中的数据存放的起始地址索引。
- 是此引用中直接或间接地查找到对象所属数据类型在方法曲中的存储的类型信息。
returnAddress类型目前很少见,它是为字节码执行jsr、jsr_w和ret服务的,指向一条字节码指令的地址,很古老的java虚拟机使用这几条执行来实现异常处理,现在已经由异常表来代替。
Java语言明确的64位数据类型只有loan和double两种(reference类型由可能是32位也可能是64位)。long和double用两个相邻的slot来存放一个64位的数据,虚拟机不允许单独访问其中的某一个
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法(非static的方法),那局部变量表中的第0位索引的slot默认是用于传递方法所属对象的实例引用,在方法中可以通过关键字“this”访问到。其余参数按照参数表顺序排列,占用从1开始的局部变量slot,分配完毕后,在根据方法体内部定义的变量顺序和作用域分配其余的slot。
操作数栈
操作数栈(Operand Stack)是一个后入先出LIFO栈。同局部变量表一样,最大深度也在编译的时候写入到Code属性的max_stacks数据项中。操作数栈中每个元素可以是任意类型,包括long和double,32位占1个容量,64位占2个容量。
方法开始执行时,这个方法操作数栈是空的,在执行过程中,会有各种字节码执行往操作数栈中写入和提取,也就是出栈/入栈操作,
例如,在做算术运算的时候是通过操作数栈来进行,调用其他方法的时候是通过操作数栈来进行参数传递的。
例如,整数加法的字节码指令是iadd在运行的时候操作数栈中最接近栈顶的2个元素已经存入了2个int型的数值,当执行这个指令时,会将这2个int值出栈并相加,然后将结果入栈。
动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用 ,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存有大量的符号引用,方法调用指令就以常量池中指向方法的符号引用作为参数。
符号引用
一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种为静态解析
一部分将在每一次运行期间转化为直接引用,这部分称为动态连接
方法返回地址
当方法开始执行后,只有两种方式可以退出这个方法。
- 正常返回,执行引擎遇到任意一个方法返回的字节码指令
- 异常返回,并且这个异常没有在方法体内得到处理,
方法退出之后,都需要返回到方法被调用的为止,程序才能继续执行,