jvm深度学习(3):java方法的运行与虚拟机栈
方法的执行
虚拟机栈是线程运行 java 方法所需的数据,指令、返回地址。其实在我们实际的代码中,一个线程是可以运行多个方法的。 比如:
这段代码很简单,就是起一个 main 方法,在 main 方法运行中调用 A 方法,A 方法中调用 B 方法,B 方法中运行 C 方法。
我们把代码跑起来,线程 1 来运行这段代码, 线程 1 跑起来,就会有一个对应 的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。 比如 main 开始运行,打包一个栈帧送入到虚拟机栈。
执行main方法
执行A()
执行B()
执行C()
C 方法运行完了,C 方法出栈,接着 B 方法运行完了,B 方法出栈、接着 A 方法运行完了,A 方法出栈,最后 main 方法运行完了,main 方法这个栈帧就 出栈了。
这个就是 Java 方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。
操作数栈:执行引擎的一个工作区。
操作系统: CPU + 缓存 + 主内存
虚拟机(模拟版的操作系统): 执行引擎 + 操作数栈 +栈、 堆
方法在栈帧中是如何操作运行的?
先写简单的代码如下:
package com.imooc.firstappdemo.jvm;
/**
* @author TofuCai
* 栈帧的执行对内存的影响
*/
public class TofuCai {
public int work() throws Exception {
int x = 1;
int y = 2;
int z = (x+y)*10;
return z;
}
public static void main(String[] args) throws Exception{
TofuCai tofuCai = new TofuCai();
tofuCai.work();
}
}
cmd到TofuCai.class文件目录下
选用下面命令进行反汇编 javap -c TofuCai.class
执行结果如下
首先大概清楚方法的程序流程
根据字节码对于work()方法的执行流程进行解析:
首先需要知道字节码各个指令的含义,这里有个腾讯提供的文档可以帮助我们解析,字节码助记码解释地址:https://cloud.tencent.com/developer/article/1333540
根据文档查得 iconst指令:
根据文档查得 istore指令:
由此我们可以得知这两个指令得作用是:
1、将一个常量值为1得数压到操作数栈;
2、将当前操作数栈的值存储到下标为1的局部变量表中
注意:
1、此时局部变量表中首位有个this表示的是当前类的实例,如果该方法为静态方法那么没有this;
2、iconst后面跟的值是数值,而istore后面跟的是值局部表量表的位置。
上面两个指令对应到代码就是:int x = 1;
同理,指令也是同上一样的,先将数值为2的数压入操作数栈,然后将该数值存入到下标为2的局部变量表。对应的代码就是:int y = 2;
下面看看接下来的流程:
首先看iload的指令:
iadd指令:
bipush指令:
imul指令:
综上流程如下:
1、从局部变量中将下标为1的数压入到操作数栈中;
2、从局部变量中将下标为2的数压入到操作数栈中;
3、将操作数栈的数值进行加法运算;
4、将常量10压入到操作数栈中;
5、将操作数栈的数值进行乘法运算;
6、将操作数栈的数值存入到局部变量表中
以上6步执行流程对应到代码为:int z = (x+y)*10;
注意:无论是iadd还是imul事实上他们都分为三步进行执行:1、操作数栈的两个数依次出栈到执行引擎(相当于jvm的cpu);2、将这两个数进行运算;3、将运算得出的结果压入到操作数栈的栈顶。
这里有个点特别有意思,执行引擎在执行完运算后,并没有直接将结果存入局部变量表,而是又压入到了操作数栈,这是为什么呢?
我们知道,jvm的执行引擎类似CPU的角色。既然如此,执行引擎就没有存储数据的功能,而操作数栈类似一个高速缓存,因此存储任务就交给了操作数栈最恰当不过,当进行大批量数据运算,不断进行中间数据的入栈出栈,极大的提高数据运算效率。
剩下最后两个指令:
根据文档得ireturn指令:
执行流程:
1、我们知道iload_3是将局部变量表中下标为3的数据压入到操作数栈中;
2、执行引擎执行将操作数栈返回给调用方。
到此整个work()方法执行结束。
结语:
为何需要操作数栈?
操作数栈在整个jvm中扮演着高速缓存的角色,由于jvm的执行引擎不能够存储数据,这就需要一个高速缓存能够能执行引擎快速存放他的执行中间结果,并快速获取。所以操作数栈是执行引擎在执行过程极其重要的部分。