• JVM字节码(三)


    接下来,就是分析MyTest1第二个方法,肯定不是int getA()就是void setA(int a)。在构造方法之后,我们读到了0x0001,这是一个访问标志为ACC_PUBLIC的方法,方法名的索引值为0x000e,类型描述符为0x000f,在常量池中分别对应getA和()I,所以我们能判定这是我们之前所定义的int getA()方法。

    往后读取的是getA()的属性数量,这里是0x0001,有一个属性。于是我们往后读取两个字节,0x0009,在常量池中第九个元素是Code,于是根据Code_attribute的结构,我们还要往后读取4个字节0x0000002f,对应的十进制为47,代表这个attribute除了attribute_name_index和attribute_length之外,还占据47个字节。

    这个属性中,max_stack是0x0001,代表操作数栈的最大深度为1。max_locals是0x0001,代表方法内创建的局部变量也为1。code_length为0x00000005,代表getA()方法编译后可供JVM执行的字节码占据5个字节,于是我们往后读取五个字节:2a b4 00 02 ac。这里我们看下getA()反编译后对应的字节码助记符:

      public int getA();
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field a:I
             4: ireturn
          LineNumberTable:
            line 7: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0       5     0  this   Lcom/leolin/jvm/bytecode/MyTest1;  
    

      

    Code下共有3条字节码指令,这里我们依旧需要根据Oracle的JVM官方文档来分析:

    • aload_0这条指令已经分析过了,对应指令0x2a,与上面5个字节码指令第一个字节码指令对应。
    • getfield代表从对象中获取字段的值,对应指令0xb4。这里需要传入一个参数0x0002,在常量池中代表int类型的字段a。所以整体指令为:b4 00 02,对应上面5个字节第二个到第四个字节码指令。
    • ireturn代表从方法中返回一个int类型的结果,对应指令0xac。对应上面第五个字节码指令。

    往后的两个字节是异常表长度,int getA()我们没抛出任何异常,所以这里是0x0000,没有异常表。之后就是属性长度和属性表,属性长度是0x0002,有两个属性。我们先来读取第一个属性对应常量池的索引:0x000a,这里是LineNumberTable。往后读取4个字节0x00000006是attribute_length,代表距离这个属性读取完毕还有6个字节。line_number_table_length为0x0001,代表有一个映射关系,start_pc是0x0000,line_number是0x0007,至此第一个属性我们分析完毕。

    接下来是第二个属性,先看常量池索引0x000b,在常量池中对应的是LocalVariableTable,根据LocalVariableTable_attribute的结构,我们往后读取四个字节0x0000000c,代表在attribute_length字段之后,还要读取12个字节这个属性才结束。我们先来读取变量表的长度local_variable_table_length,这里是0x0001,只有一个局部变量。之后start_pc为0x0000、length为0x0005、name_index为0x000c,对应常量池this、descriptor_index为0x000d,对应常量池Lcom/leolin/jvm/bytecode/MyTest1;、index为0x0000。

    剩下就是第三个方法了,也就是我们之前定义的void setA(int a)。这里的访问标志依旧是0x0001,即ACC_PUBLIC。这个方法的方法名索引和描述符索引分别是0x0010和0x0011,转换成十进制为16和17,对应常量池setA和(I)V。之后是属性数量0x0001,这个方法只有一个属性,根据Code_attribute的定义,往后读取两个字节0x0009为Code,再往后读取四个字节为0x0000003e,代表还要往后读取62个字节,这个属性才结束。max_stack是0x0002,操作数栈的最大长度为2。max_locals是0x0002,方法执行期间最大局部变量数为2。code_length为0x00000006,方法经过编译后对应的字节码指令占据6个字节的长度。我们读取后面6个字节:2a 1b b5 00 02 b1,然后看下void setA(int a)对应的字节码指令:

      public void setA(int);
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: iload_1
             2: putfield      #2                  // Field a:I
             5: return
          LineNumberTable:
            line 11: 0
            line 12: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0       6     0  this   Lcom/leolin/jvm/bytecode/MyTest1;
                   0       6     1     a   I  
    

      

    这里我们依旧尝试将指令助记符翻译成上面对应的6个字节:

    • 第一个aload_0对应的字节码指令0x2a,对应第一个字节码指令。
    • 第二个iload_1对应字节码指令0x1b,表示从局部变量表加载索引为1的变量到栈顶,对应第二个字节码指令。
    • 第三个putfield对应的字节码指令0xb5,这里接收一个参数为0x0002,为MyTest1对象的字段a,表示将栈顶的值赋予到MyTest1对象的字段a中。整体指令为0xb5 00 02。对应上面第三到第五个字节码指令。
    • 第四个return对应的字节码指令是0xb1。

    至此,上面方法的字节码指令对应完毕。这个方法也没有抛出异常,所以异常表数量为0x0000。接着是属性长度0x0002,这个方法有两个属性。第一个属性是0x000a,在常量池中对应LineNumberTable,往后读取四个字节0x0000000a,代表还要读取10个字节才结束这个属性,往后读取十个字节:00 02 00 00 00 0b 00 05 00 0c。

    line_number_table_length为0x0002,有两个映射关系。第一对映射start_pc为0x0000,line_number为0x000b;第二对映射start_pc为0x0005,line_number为0x000c。至此,第一个属性分析完毕。

    第二个属性是0x000b,对应常量池LocalVariableTable,根据LocalVariableTable_attribute的定义,我们往后读取四个字节0x00000016,这个属性还需要读取22个字节才结束。往后读取两个字节0x0002是局部变量表长度,这里有两个局部变量。第一个变量的start_pc是0x0000;length是0x0006;name_index是0x000c,对应常量池this;descriptor_index是0x000d,对应常量池Lcom/leolin/jvm/bytecode/MyTest1;;index是0x0000。第二个变量的start_pc是0x0000;length是0x0006;name_index是0x0005,对应常量池a;descriptor_index是0x0006,对应常量池I;index是0x0001。

    至此,我们三个方法都分析完毕了。

    class文件的方法之后,就是属性了,这里我们读取两个字节0x0001,MyTest1有一个属性。接着我们根据attribute_info往后读取两个字节0x0012,这里只想常量值第18个元素SourceFile,我们来看下SourceFile_attribute:

    SourceFile_attribute {  
    	u2 attribute_name_index;  
    	u4 attribute_length;  
    	u2 sourcefile_index;  
    } 
    

      

    我们先往后读取四个字节0x00000002,代表这个属性之后的长度,还有两个字节。sourcefile_index代表常量池索引,这里是0x0013,十进制为19,对应常量池元素MyTest1.java。

    至此,整个MyTest1.class文件我们都分析完毕了。

  • 相关阅读:
    jdbc(插入大对象及读取大对象、存储过程)
    jdbc批量插入操作(addBatch)
    javase(Properties集合及学生对象信息录入文本中案例)
    javase模拟斗地主洗牌和发牌(54)
    javase套接字编程
    javase网络编程
    javase多线程复制
    javase文件切割及融合
    设计原则
    模板方法模式
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/12882160.html
Copyright © 2020-2023  润新知