对于Java程序而言,每个线程在执行时,都有一个PC计数器与一个Java栈。PC计数器以字节为单位记录当前运行位置距离方法开头的偏移量,它的作用类似于ARM架构CPU的PC寄存器与x86架构CPU的IP寄存器,不同的是PC计数器只对当前方法有效,Java虚拟机通过他的值来取指令执行。Java栈用于记录Java方法调用的“活动记录”,Java栈以帧为单位保存线程运行的状态,每调用一个方法,就会分配一个新的栈帧压入Java栈上,每从一个方法返回,则弹出并撤销相应的栈帧,每个栈帧包括局部变量区,求值栈和其他一些信息,局部变量区用于存储方法的参数和局部变量,其中参数按源码中从左到右的顺序保存在局部变量开头的几个slot中。求值栈用于保存求值的中间结果和调用别的方法的参数等
我们以一个demo为例,来看jvm执行的一个基本流程
/**
* Created by leaves on 2016/11/8.
*/
public class Test2222 {
public static final int mStatic = 1111111111;
public static int mStatic2 = 222222222;
public String minstance = "asdf";
public int anum = 1;
public static void main(String[] args) {
int a=11;
int b=20;
int sum;
int c=2;
sum =a+b;
Test2222 test2= new Test2222();
sum = test2.add(a,b);
}
public int add(int a ,int b){
int sum =10;
sum=a+b;
return sum;
}
}
通过javap -verbose .\Test2222 来反编译class文件,这里主要看main代码段
public static void main(java.lang.String[]);
Code:
Stack=3, Locals=6, Args_size=1
0: bipush 11
2: istore_1
3: bipush 20
5: istore_2
6: iconst_2
7: istore 4
9: iload_1
10: iload_2
11: iadd
12: istore_3
13: new #5; //class Test2222
16: dup
17: invokespecial #6; //Method "<init>":()V
20: astore 5
22: aload 5
24: iload_1
25: iload_2
26: invokevirtual #7; //Method add:(II)I
29: istore_3
30: return
这里我们看main函数
Stack=3表示栈的大小为3, Locals=6表示本地变量占用6个单元(字节), Args_size=1表示一个参数
初始时:
第一条指令bipush
将一个byte型常量值推送至栈顶
第二条指令istore_1
可以分为两部分,第一部分istore表示jvm指令集中的store指令,i是前缀,表示 操作类型为int型,store表示将栈顶指定的数值存储到局部变量,第二部分下划线右边的数字,表示具体要操作哪个局部变量,这里数字 1表示第二个局部变量
第三条bipush 20 第四条istore_2 同理
第4条指令iconst_2 int型常量值2进栈
第5条指令istore 4 将栈顶int型数值存入指定的局部变量4
第 6 7 条指令iload_1 iload_2 分别将局部变量1 2 位置的值入栈
第8条指令add 栈顶两int型数值相加,并且结果进栈
第9条指令istore_3 将栈顶int型数值存入3位置局部变量
第10条指令new 创建一个对象,并且其引用进栈,后面的#5表示类的类型是常量池中5所指向的类型,这里是Test2222
第11条指令invokespecial #6 调用实例初始化方法 这里是Test2222."<init>":()V
我们知道这个方法没有返回值,当方法调用回来后
第12条指令astore 5
将栈顶数值(objectref)存入局部变量数组中指定下标
第13条指令 aload 5
当前frame的局部变量数组中下标为 5的引用型局部变量进栈
第 14 15条指令同样是入栈指令
这里应该是把当前的栈中的值传递给新的函数,而自己本身的栈会清空,因为我们看到第17条指令istore_3将栈顶int型数值存入第四个局部变量,这个栈顶是函数返回过来的,而我们调用函数之前如果不情况当前栈,栈已经满了,放不下数据了,我们可以在源文件中 sum = test2.add(a,b);后面再加几条代码看看,如果里面有进栈操作,则说明应该是调用函数的时候清空了当前栈(纯属猜测)
我们看下add函数
public int add(int, int);
Code:
Stack=2, Locals=4, Args_size=3
0: bipush 10
2: istore_3
3: iload_1
4: iload_2
5: iadd
6: istore_3
7: iload_3
8: ireturn
这里add函数最后ireturn没有画上去,后面会不上,main函数里面最后的return指令也是一样的
bipush 将一个byte型常量值推送至栈顶
istore_3 将栈顶int型数值存入第四个局部变量
iload_1 iload_2将两个局部变量入栈
iadd 栈顶两int型数值相加,并且结果进栈
istore_3 iload_3先将栈顶的int存入局部变量索引为3出,再将局部变量索引为3出的变量入栈
ireturn将当前栈顶int返回
随后add函数的栈帧将撤销
istore_3
将栈顶int型数值存入索引为3的局部变量(sum)
最后return表示当前方法返回void
这样我们就了解了jvm整个的一个执行流程