平时谈论起JVM内存模型,大家就会说堆、方法区、程序计数器、java虚拟机栈、native虚拟机栈。栈帧就是虚拟机栈里的概念。
编译时,编译器会将“方法”转换成字节码执行,运行时,虚拟机会为每个方法分配一个栈帧,字节码指令执行的数据结构就是栈帧。因为虚拟机栈是LIFO(后进先出),所以当前线程正在活动的栈帧,也就是栈顶的栈帧(JVM称为CurrentFrame)。
栈帧主要包含4个部分:
- 局部变量表(本文介绍)
- 操作数栈(本文介绍)
- 动态链接(本文不介绍)
- 方法返回地址(本文不介绍)
一:局部变量表
局部变量表,存储的就是某个方法运行期间,会使用或创建的变量(local variable),以数组方式存储。一个存储单位称之为slot(槽)。一个方法的局部变量表长度(slot的个数,也就是数组的长度)是在编译器就已经决定了的,我们能在class文件里找到这个值。
局部变量表大概如下所示:
- 0位置:每个方法的局部变量表,第一个位置必须是this。
- 方法参数列表:方法参数列表排在this后面。
- 方法内部创建的变量:方法内部创建变量排在方法参数列表后面。譬如我们在方法里声明了一个int值,那么在局部变量表后面就会新增一个slot,存储这个int变量。但是需要注意的是,如果你只是new Object,却并没有定义变量,那么是不会增加slot的。
需要注意的是,int、boolean、char、Object这种都只占一个slot,如果遇到long或者double类型的,则占用两个slot来存储。
具体来看个例子:
public void main(String test){
double b = 2.0d;
}
该方法编译后,使用命令查看该方法的字节码(javap -v yourClass.class):
可以看到这个方法的局部变量表长度是4,在看下详细内容,首先排在第0位的是this,第1位的是参数test,第2位的是double d这个变量。重点来了,刚刚说到long和double类型占2个slot,所以虽然是3个变量,但是长度为4,因为最后一个d占了2个slot。
再看个非静态内部类的例子:
public class InnerDemo {
private class InnerClass {
String name;
public InnerClass(String name) {
super();
}
}
可以看到非静态内部类的构造方法,局部变量表长度为3,主要是因为变量表里的第一个变量this,占了2个slot,但是正常的类只会占一个,猜测是因为非静态内部类在编译时编译器给他加了一个外部类引用导致,所以其实是2个对象,占了2个slot。
二:操作数栈
操作数栈,存放的是运行该方法时放入的指令,类似于局部变量表,其操作数栈最大深度也是在编译期间就已经决定了,也能在class文件中找到该值。
这么说其实挺抽象的,举个例子:
public class InnerDemo {
public void main() {
new InnerClass("lxx");
new InnerClass("lxx", "lxx2");
}
private class InnerClass {
String name;
public InnerClass(String temp) {
super();
}
public InnerClass(String temp, String temp2) {
super();
}
}
}
看一下main方法,该方法的操作数栈深度为5(Code-stack)中。main方法中主要有两行代码。
- new InnerClass("lxx"):执行该语句,总共有4个指令(见图),4个指令按照顺序压入操作数栈中(当前操作数栈大小为4)。执行完4个指令后,执行了pop,将当前操作数栈清空。
- new InnerClass("lxx","lxx2"):执行该语句,总共有5个指令(见图),5个指令按照顺序压入操作数栈中(当前操作数栈大小为5)。执行完5个指令后,执行了pop,将当前操作数栈清空。
所以对于main方法,操作数栈最长深度为5。
以上是我自己的理解,如有错误欢迎指出。