Java字节码运行过程

401 阅读5分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情

简单的示例 1.示例源码 先来看我们的例子代码,源码如下:

public class Test{ public static void main(String[] args){ Integer a = 1; Integer b = 2; Integer c = a + b; } }

2.main函数的字节码展示 使用javac Test.java进行编译,然后使用javap -v Test.class查看该java文件的字节码,为了排除干扰,去除了很多不必要的字节码

` *** 省略部分字节码

Constant pool:

#1 = Methodref #5.#14 // java/lang/Object."":()V

#2 = Methodref #15.#16 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

#3 = Methodref #15.#17 // java/lang/Integer.intValue:()I
*** 省略部分字节码

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
10: aload_1
11: invokevirtual #3 // Method java/lang/Integer.intValue:()I
14: aload_2
15: invokevirtual #3 // Method java/lang/Integer.intValue:()I
18: iadd
19: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: astore_3
23: return `

3.字节码指令运行过程 接下来分析Code中字节码运行的过程。这里说一下,每个指令前的数字为指令在寄存器中的偏移量。

0: iconst_1 将int常量1进行放入操作数栈。这里稍微做个拓展,如果将float常量2进行入栈操作,name该指令是fconst_2,详细的指令种类及意义请查看下一章 Java字节码指令详解。

image.png

1: invokestatic #2 调用常量池中序号为#2的静态方法,这里调用的是 Integer.valueOf()方法,表示将该int类型进行装箱操作,变为Integer类型

4: astore_1 在索引为1的位置将第一个操作数出栈(一个Integer值)并且将其存进本地变量,相当于变量a。

image.png

5: iconst_2 将int常量2进行放入操作数栈

image.png

6: invokestatic #2 调用常量池中序号为#2的静态方法,这里调用的是 Integer.valueOf()方法,表示将该int类型进行装箱操作,变为Integer类型

9: astore_2 在索引为2的位置将第一个操作数出栈(一个Integer值)并且将其存进本地变量,相当于变量b。

image.png

10: aload_1 从索引1的本地变量中加载一个int值,放入操作数栈

image.png

11: invokevirtual #3 调用常量池中序号为#3的实例方法,这里调用的是 Integer.intValue()方法

14: aload_2 从索引1的本地变量中加载一个int值,放入操作数栈

image.png

15: invokevirtual #3 调用常量池中序号为#3的实例方法,这里调用的是 Integer.intValue()方法

18: iadd 把操作数栈中的前两个int值出栈并相加,将相加的结果放入操作数栈。

image.png

19: invokestatic #2调用常量池中序号为#2的静态方法,这里调用的是 Integer.valueOf()方法

22: astore_3 在索引为3的位置将第一个操作数出栈(一个Integer值)并且将其存进本地变量,相当于变量c。

image.png

23: return 方法结束

方法调用 上面的示例是比较简单的,而且只有一个main函数,接下来将展示在多个函数时候字节码的形式以及运行的具体过程。这里就直接拿参考文章的示例,原文写得真的很好,有条件可以去看英文原文。 字节码的介绍

1.示例源码

public class Test{
    public static void main(String[] args){
        int a = 1;
        int b = 2;
        int c = calc(1,2);
    }


    public static int calc(int a,int b){
        return (int) Math.sqrt(Math.pow(a,2)+Math.pow(b,2));
    }
}

2.字节码展示

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
     0: iconst_1
  1: istore_1
  2: iconst_2
  3: istore_2
  4: iconst_1
  5: iconst_2
  6: invokestatic  #2                  // Method calc:(II)I
     9: istore_3
 10: return

static int calc(int, int);
descriptor: (II)I
flags: ACC_STATIC
Code:
stack=6, locals=2, args_size=2
     0: iload_0
  1: i2d
  2: ldc2_w        #3                  // double 2.0d
     5: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
     8: iload_1
  9: i2d
 10: ldc2_w        #3                  // double 2.0d
     13: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
     16: dadd
 17: invokestatic  #6                  // Method java/lang/Math.sqrt:(D)D
     20: d2i
 21: ireturn
  1. 指令执行过程详解 上面就是main方法和calc方法的字节码,由于main方法的指令跟上个例子很相似,唯一不同的是 c=a+b变为由calc方法去执行并且返回。这里就不再赘述main方法,接下来主要讲解calc方法的执行过程。

0: iload_0 将方法中第一个参数入栈

image.png

1: i2d将int类型转为double类型

2: ldc2_w #3 将常量池序号为#3的long型常量从常量池推送至栈顶(宽索引)

image.png

`5: invokestatic #5` 调用静态方法:Math.pow:() ,并且将结果放入栈顶

image.png

`8: iload_1`\

9: i2d
10: ldc2_w #3
13: invokestatic #5
以上的指令跟上一个一样,进行平方运算

image.png

`16: dadd` 将result和result2相加,并推入栈顶

image.png

`17: invokestatic #6` 调用Math.sqrt()方法

20: d2i 将double类型转为int类型

21: ireturn 返回int类型的数值

实例调用

修改上面的代码,加入对象,并调用对象的方法。

public class Test { public static void main(String[] args){

   Point a  =new Point (1,2);
   Point b = new Point (3,4);
   int c = a.area(b);
}

static class Point{
    private int x;
    private int y;

    public Point(int x,int y){
        this.x = x;
        this.y = y;
    }
    
    public int  area(Point p){
        int length  = Math.abs(p.y-this.y);
        int width  = Math.abs(p.x-this.x);
        return length*width;
    }
}

}

使用javap -v Test查看编译后的字节码:

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=4, args_size=1
      0: new           #2                  // class Test3$Point                                
      3: dup                                                                                   
   4: iconst_1                                                                              
   5: iconst_2                                                                              
   6: invokespecial #3                  // Method Test3$Point."<init>":(II)V                
      9: astore_1                                                                              
  10: new           #2                  // class Test3$Point                                
          13: dup                                                                                   
  14: iconst_3                                                                              
  15: iconst_4                                                                              
  16: invokespecial #3                  // Method Test3$Point."<init>":(II)V                
      19: astore_2                                                                              
  20: aload_1                                                                               
  21: aload_2                                                                               
  22: invokevirtual #4                  // Method Test3$Point.area:(LTest3$Point;)I         
      25: istore_3                                                                              
  26: return

这个main方法比上一个例子多了几个新的指令:new,dup,invokespecial

new new 指令与编程语言中的 new 运算符类似,它根据传入的操作数所指定类型来创建对象(这是对 Point 类的符号引用)。

image.png

`iconst_1``iconst_2``invokespecial`,将x,y的值(1,2)压入栈顶,接下来进行Point初始化工作,将x,y的值进行赋值。初始化完成后会将栈顶的三个操作引用销毁,只留下最初的Point的对象引用。

image.png

`astore_1` 将该Point引用出栈,并将其赋值到索引1所保存的本地变量(astore_1中的a表明这是一个引用值)

image.png

接下来进行第二个Point实例的初始化和赋值操作

image.png

-   `20: aload_1`,`21: aload_2` 将a,b的Point实例的引用入栈
  • 22: invokevirtual #4 调用 area方法,

  • 25: istore_3 将返回值放入索引3中(即赋值给c)

  • return 方法结束

创作不易,如果这篇文章对你有用,请点个赞谢谢♪(・ω・)ノ!