虚拟机栈概念
目录
-
虚拟机栈是线程独占区域,就是每个线程都拥有自己的虚拟机栈内存
-
为虚拟机执行Java方法服务,储存运行时的数据信息,由Java代码写的方法
-
虚拟机栈由栈帧组成,一个方法构成一个栈帧
-
栈帧由局部变量表、操作数栈、动态链接、返回地址组成,还有一些其它无关的数据
-
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程
如下图(局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,this,如果是静态方法就没有this)
栈帧
虚拟机栈就是由一个一个栈帧组成,一个方法就是一个栈帧,栈帧又由局部变量表、操作数栈、动态链接、返回地址构成(其它不重要的信息忽略),每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程
1、局部变量表
方法内部定义的变量,都由栈帧的局部变量表记录保存,直到方法结束,栈帧释放时局部变量表也会释放,通过索引来访问(静态方法下标0记录的还是正常的参数,因为它属于类,普通方法下标0记录的是对象的实例引用this)
2、操作数栈
方法内部需要进行的逻辑运算的数据都由操作数栈来存放(大致操作:从局部变量表取出到操作数栈中,然后从操作数栈取出进行运算,将运算结果压入操作数栈顶,最终出栈并将运算结果存储到局部变量表),操作执行,通过压栈和出栈来访问
3、动态链接
多态时确定具体类型
符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里(.class 文件),名字是知道了,但是Java真正运行起来的时候,如何靠这个名字(符号引用)找到相应的类和方法,需要解析成相应的直接引用,利用直接引用来准确地找到相应的类和方法
4、返回地址
记录的是返回代码的位置,找到位置,继续执行代码
引用专业比较的话:
当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理
无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中很可能会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息
方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等
代码演示
1、代码
分析,方法两个,main test,main方法局部变量表最终两个参数parm,res,test局部变量表两个参数i,parm
用命令行的命令对class文件进返编译,javap -c Test5,如下,已经附上解释
Compiled from "Test5.java"
public class Test5 {
public Test5();//默认构造方法
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//main方法的栈帧
public static void main(java.lang.String[]);//主函数 参数args入栈顶,出栈存入局部变量表下标为0的地方
Code:
0: iconst_4 //1、变量int值4压入操作数栈顶
1: istore_1 //2、4从栈顶弹出,存入局部变量表下标为1的地方
2: iload_1 //3、取局部变量表下标为1的值,就是取parm=4
3: invokestatic #2 //4、Method test:(I)I调用静态方法,并且传值4进入test方法
6: istore_2 //13、将返回值存入局部变量表下标为2的地方
7: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
10: iload_2 //14、取出局部变量表下标为2的变量res,压栈
11: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
14: return
//test方法的栈帧 静态方法-局部变量表0的位置有方法参数存方法参数,无方法参数存局部变量 普通方法-局部变量表0的位置存的是this,指的是实例的引用
//首先局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用
public static int test(int);//参数压入栈顶,出栈,存入局部变量表下标0的位置
Code:
0: iconst_1 //5、变量int值1压入操作数栈顶
1: istore_1 //6、1从栈顶弹出,存入局部变量表下标为1的地方
2: iload_1 //7、取局部变量表下标为1的值,就是取i=1压入操作数栈顶
3: iload_0 //8、取局部变量表下标为0的值,就是取方法参数parm=4压入操作数栈顶,此时操作数栈有4、1
4: iadd //9、从操作数栈中弹出两个int值进行相加操作,相加的结果5压栈
5: istore_1 //10、5从栈顶弹出,存入局部变量表1的地方
6: iload_1 //11、取局部变量表下标为1的值,就是取i=5压操作数栈
7: ireturn //12、5出栈,返回到上面
}
2、代码----i++ ++i解释
public class Test5 {
public static void main(String[] args) {
int i = 1;
i = i++;
i = ++i;
}
}
反编译字节码及其解释
Compiled from "Test5.java"
public class Test5 {
public Test5();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 //int值1进操作数栈顶
1: istore_1 //值1出操作数栈,存入局部变量表下标为1处
//i = i++
2: iload_1 //取局部变量表下标1的值1压入操作数栈中
3: iinc 1, 1 //局部变量表的值+1 就是为2
6: istore_1 //值1出操作数栈,存入局部变量表下标为1处,局部变量表又变成1
//i = ++i
7: iinc 1, 1 //局部变量表的值+1 就是为2
10: iload_1 //取局部变量表下标1的值2压入操作数栈中
11: istore_1 //值2出操作数栈,存入局部变量表下标为1处,局部变量表还是2
12: return
}
3、代码----对于i++ ++i参与运算
public class Test5 {
public static void main(String[] args) {
int i= 1;
i = i++ + 3;
i = ++i + 3;
}
}
反编译字节码及其解释
Compiled from "Test5.java"
public class Test5 {
public Test5();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1 //int 1压入操作数栈顶
1: istore_1 //值1 操作数栈顶出栈,存入局部变量表下标为1处
//i = i++ + 3;
2: iload_1 //从局部变量表下标1处取值1,压入操作数栈顶
3: iinc 1, 1 //局部变量表下标1处 +1 此时为2
6: iconst_3 //int 3 压入操作数栈顶
7: iadd //出栈 相加 3+1=4,4入操作数栈顶
8: istore_1 //出栈,存入局部变量表下标为1处
//i = ++i + 3;
9: iinc 1, 1 //局部变量表下标1处 +1 此时为2
12: iload_1 //取局部变量表下标1处的值压入操作数栈顶
13: iconst_3 //3 压入操作数栈顶
14: iadd //3出栈,2出栈 3+2=5,5入操作数栈
15: istore_1 //5出栈,存入局部变量表下标1处
16: return //结束,释放栈帧内存
}