方法的执行

虚拟机栈是线程运行 java 方法所需的数据,指令、返回地址。其实在我们实际的代码中,一个线程是可以运行多个方法的。 比如:

java打调用栈的实例 java方法调用栈_虚拟机栈

这段代码很简单,就是起一个 main 方法,在 main 方法运行中调用 A 方法,A 方法中调用 B 方法,B 方法中运行 C 方法。
我们把代码跑起来,线程 1 来运行这段代码, 线程 1 跑起来,就会有一个对应 的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。 比如 main 开始运行,打包一个栈帧送入到虚拟机栈。

执行main方法

java打调用栈的实例 java方法调用栈_虚拟机栈_02

执行A()

java打调用栈的实例 java方法调用栈_虚拟机栈_03

执行B()

java打调用栈的实例 java方法调用栈_操作数_04

执行C()

java打调用栈的实例 java方法调用栈_操作数栈_05

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文件目录下

java打调用栈的实例 java方法调用栈_操作数_06

选用下面命令进行反汇编 javap -c TofuCai.class

java打调用栈的实例 java方法调用栈_操作数_07

执行结果如下

java打调用栈的实例 java方法调用栈_操作数栈_08

 

首先大概清楚方法的程序流程

java打调用栈的实例 java方法调用栈_jvm_09

根据字节码对于work()方法的执行流程进行解析:

java打调用栈的实例 java方法调用栈_操作数_10

首先需要知道字节码各个指令的含义,这里有个腾讯提供的文档可以帮助我们解析,字节码助记码解释地址:https://cloud.tencent.com/developer/article/1333540

根据文档查得 iconst指令:

java打调用栈的实例 java方法调用栈_jvm_11

根据文档查得 istore指令: 

java打调用栈的实例 java方法调用栈_jvm_12

由此我们可以得知

java打调用栈的实例 java方法调用栈_虚拟机栈_13

这两个指令得作用是:

1、将一个常量值为1得数压到操作数栈;

java打调用栈的实例 java方法调用栈_操作数栈_14

2、将当前操作数栈的值存储到下标为1的局部变量表中

java打调用栈的实例 java方法调用栈_虚拟机栈_15

注意:

        1、此时局部变量表中首位有个this表示的是当前类的实例,如果该方法为静态方法那么没有this;

        2、iconst后面跟的值是数值,而istore后面跟的是值局部表量表的位置。

上面两个指令对应到代码就是:int x = 1;

同理,

java打调用栈的实例 java方法调用栈_jvm_16

指令也是同上一样的,先将数值为2的数压入操作数栈,然后将该数值存入到下标为2的局部变量表。对应的代码就是:int y = 2;

下面看看接下来的流程:

java打调用栈的实例 java方法调用栈_java打调用栈的实例_17

首先看iload的指令:

java打调用栈的实例 java方法调用栈_操作数栈_18

iadd指令:

java打调用栈的实例 java方法调用栈_操作数_19

bipush指令:

java打调用栈的实例 java方法调用栈_jvm_20

imul指令:

java打调用栈的实例 java方法调用栈_虚拟机栈_21

综上流程如下:

       1、从局部变量中将下标为1的数压入到操作数栈中;

       2、从局部变量中将下标为2的数压入到操作数栈中;

       3、将操作数栈的数值进行加法运算;

       4、将常量10压入到操作数栈中;

       5、将操作数栈的数值进行乘法运算;

       6、将操作数栈的数值存入到局部变量表中

以上6步执行流程对应到代码为:int z = (x+y)*10;

注意:无论是iadd还是imul事实上他们都分为三步进行执行:1、操作数栈的两个数依次出栈到执行引擎(相当于jvm的cpu);2、将这两个数进行运算;3、将运算得出的结果压入到操作数栈的栈顶。

这里有个点特别有意思,执行引擎在执行完运算后,并没有直接将结果存入局部变量表,而是又压入到了操作数栈,这是为什么呢?

我们知道,jvm的执行引擎类似CPU的角色。既然如此,执行引擎就没有存储数据的功能,而操作数栈类似一个高速缓存,因此存储任务就交给了操作数栈最恰当不过,当进行大批量数据运算,不断进行中间数据的入栈出栈,极大的提高数据运算效率。

剩下最后两个指令:

java打调用栈的实例 java方法调用栈_操作数_22

根据文档得ireturn指令:

java打调用栈的实例 java方法调用栈_jvm_23

   执行流程:

         1、我们知道iload_3是将局部变量表中下标为3的数据压入到操作数栈中;

         2、执行引擎执行将操作数栈返回给调用方。

到此整个work()方法执行结束。