• 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将局部变量压入操作数栈,不会删除局部变量中的值

  • 相关阅读:
    PCI 设备详解二
    PCI 设备详解一
    SKBUFFER详解
    windows中的进程和线程
    sVIrt概述
    qemu网络虚拟化之数据流向分析二
    在VC6的debug框里面输出版权信息
    [yii]Trying to get property of non-object
    yii使用CFrom调用ajax失败的记录
    VC代码生成里面的/MT /MTd /MD /MDd的意思
  • 原文地址:https://www.cnblogs.com/xxbbtt/p/14263106.html
Copyright © 2020-2023  润新知