Java代码的字节码分析

  •  new一个对象

Java代码

public class Hello {
    public static void main(String[] args) {
        Hello h = new Hello();
    }
}

然后使用下面的命令进行编译获得class文件

javac -g Hello.java

再使用下面的命令进行反编译查看字节码

javap -v  Hello.class

获得的字节码详情

Classfile java/Hello.class
  Last modified 2021-1-11; size 389 bytes
  MD5 checksum 390d0db07ea1ea82ee68a82cb82c41ad
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // Hello
   #3 = Methodref          #2.#19         // Hello."<init>":()V
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               LHello;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               h
  #17 = Utf8               SourceFile
  #18 = Utf8               Hello.java
  #19 = NameAndType        #5:#6          // "<init>":()V
  #20 = Utf8               Hello
  #21 = Utf8               java/lang/Object
{
  public Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LHello;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Hello
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
            8       1     1     h   LHello;
}
SourceFile: "Hello.java"

首先是前七行

Classfile java/Hello.class
  Last modified 2021-1-11; size 272 bytes
  MD5 checksum adc98c8855d7cc843b3556c4e922d2ac
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER

这7行的内容分别是Class文件所在的位置,最后的修改时间,文件的大小,MD5值,编译自那个文件,类的全名,jdk的版本号,主版本号(52就是jdk8),接着是该类的访问标志,ACC_PUBLIC表示是不是Public类型,ACC_SUPER是是否允许使用invokespecial字节码指令

再就是常量池

Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = Class              #14            // Hello
   #3 = Methodref          #2.#13         // Hello."<init>":()V
   #4 = Class              #15            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               Hello.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Utf8               Hello
  #15 = Utf8               java/lang/Object

常量池中主要存放文本字符串、final常量,和符号引用,符号引用包括有类和接口的全限定名、字段的名称和描述符号,方法的名称和描述符

具体分析:

 #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
 #4 = Class              #15            // java/lang/Object
 #5 = Utf8               <init>
 #6 = Utf8               ()V
 #13 = NameAndType        #5:#6          // "<init>":()V
 #15 = Utf8               java/lang/Object

这里的#1 = #4.#13,而#4=#5,#13 = #5.#6,所以我们能知道#1=java/lang/Object."<init>":()V ,表示的是该类的实例构造器的声明,因为Hello没有重写构造器,所以调用的是父类的构造方法,也就是Object的构造器方法,方法的返回值是void,也就是V

第三行类似 #3=Hello."<init>":()V

#2 = Class              #14            // Hello
#3 = Methodref          #2.#13         // Hello."<init>":()V
#5 = Utf8               <init>
#6 = Utf8               ()V
#13 = NameAndType        #5:#6          // "<init>":()V
#14 = Utf8               Hello

然后是类内部的方法描述

  public Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

这个是自动生成的构造方法,是一个无参数无返回值的方法(descriptor: ()V),公开方法(flags: ACC_PUBLIC),cpde之后就是stack最大操作数栈,jvm后面会根据这个来分配栈帧的操作深度,locals局部变量所需的存储空间,这里为1是因为有一个this,arg_size:方法参数的个数,这里是1还是因为this,再然后是方法体内容,0,1,4是字节码的行号,LineNumberTable,描述源码行号与字节码行号之间的对应关系,通常还有LocalVariableTable 该属性的作用是描述帧栈中局部变量与源码中定义的变量之间的关系。如果没有生成这项信息,那么当别人引用这个方法时,将无法获取到参数名称,取而代之的是arg0, arg1这样的占位符。 start 表示该局部变量在哪一行开始可见,length表示可见行数,Slot代表所在帧栈位置,Name是变量名称,然后是类型签名。

这里指令的含义是:

aload_0 从本地变量0中加载一个对象引用到堆内存(这里的对象引用就是java.lang.Object)

invokespecial #1 表示调用引用对象的一个实例方法(java.lang.Object.init())

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Hello
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8

new:创建Hello对象

dup:复制栈顶的引用值

invokespecial :执行对象初始化

astore_1:将引用地址值存储但编号为1的局部变量中

  • 四则运算

首先是java代码

public class Hello {
    public static void main(String[] args) {
        int a = 1,b=1,c;
        c= a+b;
        c= a-b;
        c= a*b;
        c= a/b;
    }
}

反编译后的字节码:

  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    //int类型1入栈,栈顶=1
         1: istore_1    //将栈顶的int数值出栈并存入第二个局部变量
         2: iconst_1   //int类型1入栈,栈顶=1
         3: istore_2   //将栈顶的int数值出栈并存入第三个局部变量
         4: iload_1    //将第二个局部变量值压入栈顶
         5: iload_2    //将第三个局部变量值压入栈顶
         6: iadd         //将栈顶两int类型数相加,结果入栈
         7: istore_3   //将栈顶的int数值出栈并存入第四个局部变量
         8: iload_1  //将第二个局部变量值压入栈顶
         9: iload_2  //将第三个局部变量值压入栈顶
        10: isub      //将栈顶两int类型数相减,结果入栈
        11: istore_3 //将栈顶的int数值出栈并存入第四个局部变量
        12: iload_1 //将第二个局部变量值压入栈顶
        13: iload_2 //将第三个局部变量值压入栈顶
        14: imul  //将栈顶两int类型数相乘,结果入栈
        15: istore_3   //将栈顶的int数值出栈并存入第四个局部变量
        16: iload_1 //将第二个局部变量值压入栈顶
        17: iload_2 //将第三个局部变量值压入栈顶
        18: idiv  //将栈顶两int类型数相除,结果入栈
        19: istore_3  //将栈顶的int数值出栈并存入第四个局部变量
        20: return
      LineNumberTable:
        line 3: 0
        line 4: 4
        line 5: 8
        line 6: 12
        line 7: 16
        line 8: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  args   [Ljava/lang/String;
            2      19     1     a   I
            4      17     2     b   I
            8      13     3     c   I

这次的运算,基本逻辑就是int值入栈(iconst),int值出栈(istore),完成变量a、b的声明和初始化,然后将a,b的值压入栈中,使用iadd,isub,imul,idiv进行四则运算,然后istore_3将结果存入c

其中要注意的是,store会删除栈顶值,load将局部变量压入操作数栈,不会删除局部变量中的值

  • if 和for

Java代码

import java.util.ArrayList;
import java.util.List;

public class Hello {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        for(int i = 0;i<10;i++){
            list.add(i);
        }
        for(Integer a:list){
            if(a%2 == 0){
                System.out.println(a);
            }
        }
    }
}

反编译之后的字节码

 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: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: iconst_0
         9: istore_2
        10: iload_2
        11: bipush        10
        13: if_icmpge     33
        16: aload_1
        17: iload_2
        18: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        21: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        26: pop
        27: iinc          2, 1
        30: goto          10
        33: aload_1
        34: invokeinterface #6,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        39: astore_2
        40: aload_2
        41: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
        46: ifeq          78
        49: aload_2
        50: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        55: checkcast     #9                  // class java/lang/Integer
        58: astore_3
        59: aload_3
        60: invokevirtual #10                 // Method java/lang/Integer.intValue:()I
        63: iconst_2
        64: irem
        65: ifne          75
        68: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
        71: aload_3
        72: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        75: goto          40
        78: return
      LineNumberTable:
        line 6: 0
        line 7: 8
        line 8: 16
        line 7: 27
        line 10: 33
        line 11: 59
        line 12: 68
        line 14: 75
        line 15: 78
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10      23     2     i   I
           59      16     3     a   Ljava/lang/Integer;
            0      79     0  args   [Ljava/lang/String;
            8      71     1  list   Ljava/util/List;

这一段字节码实现了第一个循环

8: iconst_0
9: istore_2
10: iload_2
11: bipush        10
13: if_icmpge     33
16: aload_1
17: iload_2
18: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
21: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
26: pop
27: iinc          2, 1
30: goto          10

8-10生成i变量并赋值为0,在将改变量压入栈顶,然后11行将10压入栈顶,在13行比较栈顶两个int的值大于等于后则跳转到33行,如果是小于的话接着到16行-26行,进行list的app的操作,然后使用27行的iiinc实现i的自增,最后是30行的goto实现跳转到第10行完成一次循环。

33: aload_1
34: invokeinterface #6,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
39: astore_2
40: aload_2
41: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
46: ifeq          78
49: aload_2
50: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
55: checkcast     #9                  // class java/lang/Integer
58: astore_3
59: aload_3
60: invokevirtual #10                 // Method java/lang/Integer.intValue:()I
63: iconst_2
64: irem
65: ifne          75
68: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
71: aload_3
72: invokevirtual #12                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
75: goto          40
78: return

这段代码实现了第二个循环加判断,这个地方用的是迭代器实现的循环,33-46行主要是通过调用迭代器的hasNext判断循环是否结束,如果结束了,46行可直接跳到78行结束循环,这个循环在75行使用goto跳到40行执行下一次循环

判断的地方首先是,49-64行首先是49-58 Interger的强转并压入栈中,然后是63-64的与2取模运算,然后是65行的ifne对计算结果也就是栈顶的判断是否为0,不为0则跳转到75行也就是条件不成立,这样就完成了条件判断if

  • 总结

经过上面几个实验可以总结出:

  1. JVM运行字节码是基于栈、局部变量数组、常量池的操作,栈中的变量主要用于运算、判断,局部变量数组、常量池就是存放数据,
  2. 基本每次都需要先将变量中的数据load到栈中再进行运算,
  3. 如果想要新生成基本类型就需要使用const操作在栈中获得数据,然后store到局部变量表中,使用的时候再load
  4. 如果想要新生成对象需要使用new-dup-invokespecial生成对象再store到局部变量表中,使用invokevirtual 调用方法,如果是接口使用invokeinterface 
  5. store会删除栈顶值,load将局部变量压入操作数栈,不会删除局部变量中的值

posted @ 2021-01-11 17:07  xxbbtt  阅读(192)  评论(0编辑  收藏  举报