深入理解java虚拟机(9):字节码执行引擎介绍-栈帧
在不同的java虚拟机里面,执行引擎在执行java代码时可能会解释执行(通过解释器进行执行)也可能会编译执行(通过即时编译器产生本地代码进行执行),也可能两者兼备,甚至还可能包含几个级别不同的编译器执行引擎。
栈帧是虚拟机进行方法调用和方法运行的数据结构,是虚拟机运行时虚拟机栈的栈元素,存储了方法的局部变量表,操作数栈,动态链接和返回地址等信息,每一个方法从调用开始到执行完成都对应着一个栈帧在虚拟机栈中出栈和入栈。在代码编译期间,一个方法的栈帧需要多大的局部变量表,多大的操作数栈就已经确定了,并且写到了方法表的code属性中,因此一个栈帧需要分配多大内存,不会受程序运行时数据影响,仅仅取决于具体的虚拟机实现。模型如下:
1、局部变量表
局部变量表是一组变量存储空间,用于存储方法中变量和参数的局部变量。局部变量表以变量槽Slot为最小单位,但是虚拟机规范中没有指定slot的内存大小,但是要求一个slot必须内存放下一个32位类型的数据,如boolean,byte,short,int,float,char,reference,referenceAddress。reference java虚拟机没有规定长度,但是必须能实现以下两个功能
1)直接或者间接从java虚拟机堆中找到数据存放的起始地址索引。
2)此引用中直接或间接的查找到对象所属数据类型在方法区中存储的类型信息。调用非实例方法时第一个slot默认存储this指针,后面依次存储参数和按照作用域和变量定义的顺序存储变量。如果当前计数器的值已经超出了某个变量的作用域,那这个变量对应的slot就可以交给其他变量来使用,某些情况slot复用会影响gc的行为。
package org.xiaofeiyang.classloader;
/**
* @author: yangchun
* @description:
* @date: Created in 2019-11-24 7:03
*/
public class LocalVaribleTest {
public static void main(String[] args){
byte[] res = new byte[64*1024*1024];
System.gc();
}
}
如果改成
package org.xiaofeiyang.classloader;
/**
* @author: yangchun
* @description:
* @date: Created in 2019-11-24 7:03
*/
public class LocalVaribleTest {
public static void main(String[] args){
{
byte[] res = new byte[64 * 1024 * 1024];
}
int a=0;
System.gc();
}
}
内存会被回收。
局部变量和类变量不一样,类变量在准备阶段赋值了零值,在初始化阶段不赋值也行。局部变量不赋初始值是不行的。
2、操作数栈
当一个方法开始执行的时候,操作数栈是空的,在方法执行过程中有字节码指令不停的写入或者提取内容。操作数栈中的元素的数据类型必须与字节码指令严格匹配,在编译程序时,编译器要严格保证这一点。
栈帧之间数据共享如下,保证方法传递时不需要进行额外的参数复制传递。
3、动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就转化为直接引用,这种转化为静态解析。另外一部分会将在每一次运行期间转化成直接引用,被称为动态链接。
4、方法返回地址
一个方法开始执行只有两种返回方式,第一种方式是执行引擎遇到任意方法返回的字节码指令,根据指令返回结果给上层调用者,称为正常返回。第二种,方法执行过程中遇到异常,并且这个异常没有在方法体中得到处理,无论是虚拟机内部异常还是athrow抛出的异常,只要在方法表中没有搜索到匹配的异常处理器,就会导致方法退出,不会给上层调用者返回任何返回值。无论哪种退出都需要返回到被调用位置,程序才能继续执行,方法返回可能需要在栈帧中存储一些信息,用来帮助它恢复它的上层方法的执行状态,调用者的pc计数器可以作为返回地址,栈帧可能保存这个数值,异常退出返回地址是要通过异常处理器表来确定,栈帧一般不会保存这部分信息。
方法退出的过程是就是把当前栈帧出栈,退出的可能操作,就是恢复上层调用方法的局部变量表和操作数栈,并且将返回结果压入调用者栈帧的操作数栈,调整pc计数器指向调用者的下一条指令。
5、附加信息
主要是java虚拟机规范中没有规定的信息,例如调试信息,