先上图
先明白几点
1.jvm stack指的是线程栈,一个线程有一个jvm stack。
2.线程里的一个方法对应一个栈帧,一个栈帧有Local Variables(本地变量表,记录了方法的参数、局部变量)、Operand Stacks(方法运算的变量引用,常量值需要压栈和弹栈)、Dynamic Linking(方法运算需要引用方法区的引用)、Return Address(记录了方法需要返回的调用者的地址)。
3.所有方法栈里的运算都要压栈和弹栈,就是Operand Stacks要频繁的发生 astore_n 和 aload_n。
对象的创建过程
1.main方法被压入线程栈底部;
2.Hello_02 h = new Hello_02()这一句的字节码:
0 new --> 在heap里开辟一个内存,放Hello_02对象,这个对象赋默认值(int类型是0,引用类型是null)把这个对象的地址压栈到main方法的Operand Stacks里;
3 dup --> 复制main方法栈里的Operand Stacks的Hello_02对象的地址,所以此时Operand Stacks里有相同的两个指向Hello_02对象的地址。
4 invokespecial --> 把复制Hello_02的地址从Operand Stacks弹出来顶上的那个,运行init构造方法,给局部变量赋初始值。对象创建完成,Operand Stacks里剩下Hello_02的地址就指向了创建完成的Hello_02。
7 astore_1 --> 把Operand Stacks 里的指向Heap里的地址弹栈,赋值给Local Variables 里的h
,到这一步Hello_02 h = new Hello_02()这一步就完成了。
为什么要DCL要加volatile呢?
对象创建的过程中,cpu会乱序执行,如果一个线程运行到 3 dup的时候进来另一个线程要用h,而此时0 new已经执行完了,所以实例已经不是null了,但是还没有4 invokespecial的init,所以heap里的对象局部变量都是默认值,还不是初始值,所以就不是程序要想要的初始化好的对象,所以要加volitale锁定内存,保证指令不乱序执行(逻辑上),让程序得到正确的初始化好的对象。
进入h.m1()
8 aload_1 -->对应了h.m1()方法,把main栈里的Local Variables的h压到Operand Stacks里。
9 invokevirtual --> 运行m1方法,这时候main栈停止,切换到m1方法栈。
0 sipush -->先解释sipush,s表示是short类型,i表示int类型(jvm达不到int类型的的数值运算会映射成int类型) s标识short,数值>127(byte类型的范围)就映射成short类型。push就是压栈。所以这条指令就是把 200 这个值压入 Operand Stacks。
3 istore_1 --> Operand Stacks里的200 弹栈,给到 Local Variables 里的i。
4 return --> 返回到main方法里的 invokevirtual。
** 返回main **
12 return --> 返回给线程的调用者。
** 同理 **
m1是有返回值的,那么m1栈执行完之后,会把结果压栈进入main栈的栈顶,此时没有变量接收,就多了一步 12 pop,把m1的返回值弹出去,扔掉。
** 再者 **
m1方法的返回值有i变量接收,就直接把m1方法的返回值给了main方法的Local Variables里的2号位置,即变量i,就是 istore_2这一步。、
** 递归 **
线程把m这个方法,根据传参依次压栈,执行完一个弹栈一个。所以如果递归有死循环或者递归跳出条件设置不合理,会栈堆满了Overfolw异常。