美文网首页
完整分析Class字节码

完整分析Class字节码

作者: Hilbert1 | 来源:发表于2020-02-01 21:36 被阅读0次

    这篇文章完整分析了一个Class字节码文件。只要你认真阅读了每一个字,你就基本掌握了Class字节码。

    首先简单的介绍下我们平时编写的Java文件是通过javac命令编译成字节码文件的,如果你不想自己编译,在Java工程的bin目录下也能找到对应的字节码文件。
    下面是源码Person类:

    package hilbert;
    
    public class Person {
        private String name;
        private int number;
        
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getNumber() {
            return number;
        }
    
        public void setNumber(int number) {
            this.number = number;
        }
    }
    

    通过javac编译成Person.class,使用UltraEdit打开如下:

    Person.class字节码文件.png

    UltraEdit是查看二进制文件的好工具,可用于分析Class字节码、图片、视频等。它也是一款不错的文本编辑器。

    下面是Java虚拟机规范第四章“Class文件格式”中对Class文件格式的描述(可以先粗略浏览下,后面会逐个分析)

    ClassFile {
        u4             magic;
        u2             minor_version;
        u2             major_version;
        u2             constant_pool_count;
        cp_info        constant_pool[constant_pool_count-1];
        u2             access_flags;
        u2             this_class;
        u2             super_class;
        u2             interfaces_count;
        u2             interfaces[interfaces_count];
        u2             fields_count;
        field_info     fields[fields_count];
        u2             methods_count;
        method_info    methods[methods_count];
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    上面的Person.class字节码文件是完全符合ClassFile文件格式的,这个完全符合包括内容和顺序都是完全符合的。我们现在就来逐个字节的分析。从Person.class字节码文件中的开始字节(0xCA,对应ClassFile中magic的第一个字节)分析到结束字节(0x1F,对应ClassFile中attribute_info attributes[attributes_count]最后一个字节)。

    在分析前,我们先了解下javap -p -verbose (或者javap -p -v,两者是一样的)命令:
    注意:javap -v 或者javap -verbose 不能解析出private方法和private字段!!!有private方法或字段的需要加上-p才能解析出来。很多文章或书籍都没提到这个。

    C:\Users\Administrator>javap -p -verbose D:\eclipse-workspace\Demo1\bin\hilbert\Per
    son.class
    
    C:\Users\Administrator>javap -p -verbose D:\eclipse-workspace\Demo1\bin\hilbert\Per
    son.class
    

    产生的结果如下:

    C:\Users\Administrator>javap -p -v D:\eclipse-workspace\Demo\bin\hilbert\Person.
    class
    Classfile /D:/eclipse-workspace/Demo/bin/hilbert/Person.class
      Last modified 2020-2-8; size 714 bytes
      MD5 checksum a53c8107b7521090395160610ffad96d
      Compiled from "Person.java"
    public class hilbert.Person
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Class              #2             // hilbert/Person
       #2 = Utf8               hilbert/Person
       #3 = Class              #4             // java/lang/Object
       #4 = Utf8               java/lang/Object
       #5 = Utf8               name
       #6 = Utf8               Ljava/lang/String;
       #7 = Utf8               number
       #8 = Utf8               I
       #9 = Utf8               <init>
      #10 = Utf8               ()V
      #11 = Utf8               Code
      #12 = Methodref          #3.#13         // java/lang/Object."<init>":()V
      #13 = NameAndType        #9:#10         // "<init>":()V
      #14 = Utf8               LineNumberTable
      #15 = Utf8               LocalVariableTable
      #16 = Utf8               this
      #17 = Utf8               Lhilbert/Person;
      #18 = Utf8               getName
      #19 = Utf8               ()Ljava/lang/String;
      #20 = Fieldref           #1.#21         // hilbert/Person.name:Ljava/lang/Stri
    ng;
      #21 = NameAndType        #5:#6          // name:Ljava/lang/String;
      #22 = Utf8               setName
      #23 = Utf8               (Ljava/lang/String;)V
      #24 = Utf8               getNumber
      #25 = Utf8               ()I
      #26 = Fieldref           #1.#27         // hilbert/Person.number:I
      #27 = NameAndType        #7:#8          // number:I
      #28 = Utf8               setNumber
      #29 = Utf8               (I)V
      #30 = Utf8               SourceFile
      #31 = Utf8               Person.java
    {
      private java.lang.String name;
        descriptor: Ljava/lang/String;
        flags: ACC_PRIVATE
    
      private int number;
        descriptor: I
        flags: ACC_PRIVATE
    
      public hilbert.Person();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #12                 // Method java/lang/Object."<init>
    ":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lhilbert/Person;
    
      public java.lang.String getName();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #20                 // Field name:Ljava/lang/String;
             4: areturn
          LineNumberTable:
            line 8: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lhilbert/Person;
    
      public void setName(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: putfield      #20                 // Field name:Ljava/lang/String;
             5: return
          LineNumberTable:
            line 12: 0
            line 13: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lhilbert/Person;
                0       6     1  name   Ljava/lang/String;
    
      public int getNumber();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #26                 // Field number:I
             4: ireturn
          LineNumberTable:
            line 16: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lhilbert/Person;
    
      public void setNumber(int);
        descriptor: (I)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: iload_1
             2: putfield      #26                 // Field number:I
             5: return
          LineNumberTable:
            line 20: 0
            line 21: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lhilbert/Person;
                0       6     1 number   I
    }
    SourceFile: "Person.java"
    

    javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息(粗斜体字不理解不要慌,后面会逐个解释)。注意:这种解析结果是方便人们阅读汇编指令的,而并非给虚拟机装载、执行的。虚拟机真正装载、执行的是上面的Person.class字节码。因此它(javap -v 反解析结果)的顺序并不完全和ClassFile字节码格式吻合,理解这点很重要。 当你完全读完了这篇文章,理解了每一个字,我相信你也能用java代码写出javap 命令,反解析字节码。

    下图展示了 Person.java、Person.class、ClassFile文件格式、JVM、javap -v 反解析Person.class结果的关系:


    Person.java、Person.class、JVM、ClassFile、反解析Person.class关系.png

    接下来按照ClassFile文件格式逐个字节分析Person.class

    1、u4 magic

    u4代表4个字节,后面的u2代表2个字节。magic 魔数 识别二进制文件格式, Class文件前4个字节表示为:0xCAFEBABE (这个是固定的,它就代表这个文件是Class文件)


    class文件魔数.png

    JPG图片魔数是前面3个字节 0xFFD8FF


    jpg图片魔数.png

    各类文件的文件头 魔数

    2、u2 minor_version

    minor_verson 2个字节表示Class文件的小版本信息 这里是0x0000


    小版本(minor_version).png

    3、u2 major_version

    major_version 2个字节表示Class文件版本的大版本信息 这里是 0x0034 转化为10进制 3*16+4=52


    大版本(major_version).png JDK版本和major_version对应关系.png

    JDK1.8对应的major_version是52。

    4、u2 constant_pool_count

    constant_pool_count表示常量池常量的数量。这里是0x0020 转化为十进制是16*2=32。代表有32个常量。但是通过刚才javap -v 反编译Persion.class可知常量池中一共有31个常量,而且第一个常量的index是1。其实可以理解有32个常量index为0的代表不引用任何常量。


    常量池中常量数量.png

    5、cp_info constant_pool[constant_pool_count-1]

    cp_info就是刚才常量池中的31个常量。
    cp_info格式如下:

    cp_info{
      u1 tag;   //第一个字节表示常量类型
      u1 info[]; //后面n个字节表示具体类型常量。
    }
    

    常量类型如下表(先粗略的看下,后面每分析一个常量项就会来查这个表,查着查着就熟悉了):


    字节码常量类型表.png

    根据上面cp_info和观察字节码常量类型表可知:任何类型常量的第一个字节(u1 tag)都表示常量的类型。接下来观察第一个字节 是 0x07 转化为10进制:7。 由字节码常量类型表可知:这是CONSTANT_Class_info常量项,接下来的2个字节表示类的名字在常量池中的索引值: 0x0002 转化为10进制 name_index = 2。

    第1个常量:

    07 //对应字节码常量类型表 Constant_Class_info
    00 02 //name_index 类名索引值 2,对应第2个常量可知,其指向的值为: hilbert/Person
    

    第2个常量

    01 //对应字节码常量类型表 Constant_Utf8_info,
    00 0E //这两个字节代表字符串的字节长度,00 0E转化为10进制为14。表示接下来14个字节为字符串内容
    68 69 6C 62 65 72 74 2F 50 65 72 73 6F 6E//这14个字节就是内容,转化为10进制再对照ASCII码表:hilbert/Person
    

    例如:第一个字符0x68 转化为10进制是104,查ASCII表可知为:h。


    ASCII字代码表.jpg
    第2个常量项具体内容.png

    每分析一个常量,可以回看下上面javap -v 反解析Person.class的结果,可以对照比较是否正确。
    接下来的分析会稍微快些:

    第3个常量

    07//对应字节码常量类型表 Constant_Class_info
    00 04//name_index 类名索引值 4,对应第4个常量可知,其指向的值为: java/lang/Object
    

    第4个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 10//这两个字节代表字符串的字节长度,转化为10进制为16。表示接下来16个字节为字符串内容
    6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 //这16个字节就是内容,转化为10进制再对照ASCII码表:java/lang/Object
    

    第5个常量

    01 //对应字节码常量类型表 Constant_Utf8_info,
    00 04//表示接下来4个字节为字符串内容
    6E 61 6D 65//name
    

    第6个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 12//表示接下来18个字节为字符串内容
    4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B//Ljava/lang/String;
    

    第7个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 06//表示接下来6个字节为字符串内容
    6E 75 6D 62 65 72 //number
    

    第8个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 01//表示接下来1个字节为字符串内容
    49//I
    

    第9个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 06//表示接下来6个字节为字符串内容
    3C 69 6E 69 74 3E //<init>
    

    第10个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 03//表示接下来3个字节为字符串内容
    28 29 56 //()V
    

    第11个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 04//表示接下来4个字节为字符串内容
    43 6F 64 65//Code
    

    第12个常量

    0A//转化为10进制为10。对应字节码常量类型表 Constant_Methodref_info,
    00 03//class_index  类名索引值 3,对应第3个常量可知,再指向第4个常量值: java/lang/Object
    00 0D//name_and_type_index 表示方法的名字,参数类型返回值类型,指向第13个常量值
    

    第13个常量

    0C//转化为10进制为12,对应字节码常量类型表 Constant_NameAndType_info,
    00 09//name_index 方法名字,指向第9个常量<init>
    00 0A//descriptor_index 描述(参数类型,返回值类型),指向第10个常量()V,表示参数为空,返回值为void
    
    

    第14个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 0F//表示接下来15个字节为字符串内容
    4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 //LineNumberTable
    

    第15个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 12//表示接下来18个字节为字符串内容
    4C 6F  63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65//LocalVariableTable
    

    第16个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 04//表示接下来4个字节为字符串内容
    74 68 69 73//this
    

    第17个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 10//表示接下来16个字节为字符串内容
    4C 68 69 6C 62 65 72 74 2F 50 65 72 73 6F 6E 3B//Lhilbert/Person;
    

    第18个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 07//表示接下来7个字节为字符串内容
    67 65 74 4E 61 6D 65 //getName
    

    第19个常量

    01//对应字节码常量类型表 Constant_Utf8_info,
    00 14//表示接下来20个字节为字符串内容
    28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 38  //()Ljava/lang/String;
    

    第20个常量

    09//对应字节码常量类型表 Constant_Fieldref_info,
    00 01//class_index 表示类名,指向第1个常量,再指向第二个常量值,即hilbert/Person
    00 15//name_and_type_index 表示字段名和字段类型,指向第21个常量值,再指向第5第6个常量值,即name:Ljava/lang/String;
    

    由Person.java可知,其中字段name的类型是String,对应字节码类型为"L"+全限定类名+";",其中全限定类名中的"."改成"/",即"Ljava/lang/String;"。所有引用类型的字节码类型都是这样表示:"L"+全限定类名+";"

    基本类型和引用类型在字节码中的对应表示:

    BaseType Character  Type         Interpretation
    B                   byte         signed  byte
    C                   char         Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16
    D                   double       double-precision floating-point value
    F                   float        single-precision floating-point value
    I                   int          integer
    J                   long         long integer
    L ClassName ;       reference    an instance of class ClassName
    S                   short        signed short
    Z                   boolean      true or false
    [                   reference    one array dimension
    [[                  reference    double array dimension
    

    第21个常量

    0C//转化为10进制为:12 对应字节码常量类型表 Constant_NameAndType_info,
    00 05//name_index 字段名,指向第5个常量,即:name
    00 06//descriptor_index 描述 字段类型 指向第6个常量,即:Ljava/lang/String;
    

    第22个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 07//表示接下来7个字节为字符串内容
    73 65  74  4E 61 6D 65 //setName
    

    第23个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 15//表示接下来21个字节为字符串内容
    28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56// (Ljava/lang/String;)V
    

    第24个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 09//表示接下来9个字节为字符串内容
    67 65 74 4E 75 6D 62 65 72 // getNumber
    

    第25个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 03//表示接下来3个字节为字符串内容
    28 29 49////()I
    

    第26个常量

    09//对应字节码常量类型表 Constant_Fieldref_info,
    00 01//class_index 表示类名,指向第1个常量,再指向第二个常量值,即hilbert/Person
    00 1B////name_and_type_index 表示字段名和字段类型,指向第27个常量值,再指向第7第8个常量值,即name:I;
    

    第27个常量

    0C//转化为10进制为:12 对应字节码常量类型表 Constant_NameAndType_info,
    00 07//name_index 字段名,指向第7个常量,即:number
    00 08//descriptor_index 描述 字段类型 指向第8个常量,即:I
    

    第28个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 09//表示接下来9个字节为字符串内容
    73 65 74 4E 75 6D 62 65 72 //setNumber
    

    第29个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 04//表示接下来4个字节为字符串内容
    28 49 29 56//(I)V
    

    第30个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 0A//表示接下来10个字节为字符串内容
    53 6F 75 72 63 65 46 69 6C 65//SourceFile
    

    第31个常量

    01//对应字节码常量类型表  Constant_Utf8_info,
    00 0B//表示接下来11个字节为字符串内容
    50 65 72 73 6F 6E 2E 6A 61 76 61//Person.java
    

    到此,常量全部解析完毕。

    6、u2 access_flags

    常量池后,紧接着的2个字节表示类的访问控制信息(access_flags)。

    标志名          取值     说明
    ACC_PUBLIC      0x0001  public类型.
    ACC_FINAL       0x0010  final类型.
    ACC_SUPER       0x0020  用于invokespecial指令
    ACC_INTERFACE   0x0200  表明这个类是一个Interface
    ACC_ABSTRACT    0x0400  abstract类型
    ACC_SYNTHETIC   0x1000  表明该类由编译器根据情况生成的,源码里无法显示定义这样的类
    ACC_ANNOTATION  0x2000  注解类型
    ACC_ENUM        0x4000  枚举类型
    

    参考上表,对应咋们的Person.class

    00 21//  0x0021 = 0x0001|0x0020,即表示类是public类型;执行父类方法,无需动态绑定,指令是:invokespecial(可以自行了解invokespecial和invokevirtual的区别)
    

    7、u2 this_class

    00 01//指向常量池中第1个常量项,再转向第2个常量项,即:hilbert/Person
    

    8、u2 super_class

    00 03//指向常量池中第3个常量项,再转向第4个常量项,即:java/lang/Object
    

    9、u2 interfaces_count

    00 00//接口数量,表示有0个接口,即没有接口
    

    10、u2 interfaces[interfaces_count]

    由于没有接口,所以此项没有数据,没有字节。

    11、u2 fields_count

    00 02//表示有2个字段
    

    12、field_info fields[fields_count]

    这项就是2个字段的具体内容
    field_info的格式如下:

    field_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    第1个字段

    u2 access_flags字段访问控制

    由如下表:

    
    标志名            取值       说明
    ACC_PUBLIC       0x0001     public类型
    ACC_PRIVATE      0x0002     private类型
    ACC_PROTECTED    0x0004     protected类型
    ACC_STATIC       0x0008     static类型.
    ACC_FINAL        0x0010     final类型.
    ACC_VOLATILE     0x0040     volatile类型.
    ACC_TRANSIENT    0x0080     transient类型,说明该字段不能被串行化.
    ACC_SYNTHETIC    0x1000     表面该成员由编译器根据情况生成的,源码里无法显示定义这样的成员
    ACC_ENUM         0x4000     枚举类型.
    

    可知:

    00 02//表示此字段是 private类型 即:ACC_PRIVATE
    

    u2 name_index 表示字段名字在常量池中的索引

    00 05//指向第5个常量,即:name
    

    u2 descriptor_index 字段描述符索引(字段类型)

    00 06//指向第6个常量,即:Ljava/lang/String;
    

    u2 attributes_count 字段属性数量

    00 00//表示没有字段属性
    

    attribute_info attributes[attributes_count] 字段中的属性信息

    由于此字段没有属性信息,直接到第二个字段。

    第2个字段

    00 02//表示此字段是 private类型 即:ACC_PRIVATE
    00 07//指向第7常量,即:number
    00 08//指向第8个常量,即:I;
    00 00//表示没有字段属性信息
    

    13、u2 methods_count

    00 05//表示有5个方法
    

    14、method_info methods[methods_count]

    这项就是2个方法的具体内容
    method_info的格式和field_info的格式是完全一致的,格式如下:

    method_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }
    

    其中方法的access_flags和字段的access_flags是不同,如下:

    标志名                  取值         说明
    ACC_PUBLIC             0x0001       public类型.
    ACC_PRIVATE            0x0002       private类型.
    ACC_PROTECTED          0x0004       protected类型.
    ACC_STATIC             0x0008       static类型.
    ACC_FINAL              0x0010       final类型.
    ACC_SYNCHRONIZED       0x0020       synchronized方法.
    ACC_BRIDGE             0x0040       桥接方法,有编译器根据情况生成.
    ACC_VARARGS            0x0080       可变参数个数的方法.
    ACC_NATIVE             0x0100       native方法.
    ACC_ABSTRACT           0x0400       abstract方法.
    ACC_STRICT             0x0800       strictfp类型(strict float point,精确浮点).
    ACC_SYNTHETIC          0x1000       synthetic类型,在源代码中不存在.
    

    第1个方法

    00 01//access_flags public类型
    00 09//name_index 方法名字索引,指向第9个常量,即<init>,<init>是构造方法的名字
    00 0A//descriptor_index 方法描述索引,即方法的参数和返回值类型,指向第10个常量,即()V。括号()内表示参数类型,此时括号里没有内容,代表没有参数。括号()后面V表示返回值,V表示void,即无返回值。
    00 01//属性数量,表示有一个属性
    //attribute_info 见下
    

    attribute_info格式

    属性不仅可以在方法里有,字段里有可以有(只是上面Person.class的2个字段里没有属性而已),类里也会有,属性内部也会有属性。下面是属性的基本结构:

    attribute_info {
        u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
        u4 attribute_length;//该属性具体内容的长度,即下面info的长度
        u1 info[attribute_length];//属性具体类容
    }
    

    不同的属性名称,对应不同的属性类型。下面列举5中常见的属性。想查看全部类型属性,可参考:[Java虚拟机规范第四章“Class文件格式”中属性格式]。(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7)
    下面5个属性格式,可以先粗略看下,后面会结合Person.class具体分析。

    ConstantValue属性

    该属性只出现于field_info中,用于描述一个常量成员域的(long、float、doule、int、short、char、byte、boolean、String等)值。其格式如下:

    ConstantValue_attribute {
        u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
        u4 attribute_length;//该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
        u2 constantvalue_index;//属性值,指向常量池中常量项的索引
    }
    
    Code属性(非常重要)

    该属性是非常重要的一个属性,只出现于method_info中,用于描述一个方法(非native和abstract方法)的内容:即源码中该方法内容编译后得到的最大栈深度,最大局部变量数、虚拟机指令、包含的其他属性等。其结构如下:

    Code_attribute {
        u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
        u4 attribute_length;//该属性具体内容的长度,即下面所有属性内容的字节长度
        u2 max_stack;//该方法执行过程中,需要最深多少栈空间
        u2 max_locals;//该方法包含最多几个局部变量。
        u4 code_length;//方法经过编译得到的指令数量
        u1 code[code_length];//具体指令内容
        u2 exception_table_length;一个方法中try/catch语句的数量
        {   u2 start_pc;//try/catch语句从那条指令开始的
            u2 end_pc;//try/catch语句到哪条指令结束。注意,只包括try语句,不包括catch
            u2 handler_pc;//表示catch语句的内容从哪条指令开始。
            u2 catch_type;//表示catch中截获的Exception或Error的名字,指向Utf8_info常量项。如果catch_type取值为0,则表示它是final{}语句块。
        } exception_table[exception_table_length];//每个try/catch语句的内容
        u2 attributes_count;//Code_attribute内部包含的其他属性数量
        attribute_info attributes[attributes_count];//Code_attribute内部包含的其他属性内容
    }
    

    Code属性中包含虚拟机指令(u1 code[code_length])下表是具体的虚拟机指令集:

    字节码   助记符             指令含义
    0x00    nop               什么都不做
    0x01    aconst_null       将null推送至栈顶
    0x02    iconst_m1         将int型-1推送至栈顶
    0x03    iconst_0          将int型0推送至栈顶
    0x04    iconst_1          将int型1推送至栈顶
    0x05    iconst_2          将int型2推送至栈顶
    0x06    iconst_3          将int型3推送至栈顶
    0x07    iconst_4          将int型4推送至栈顶
    0x08    iconst_5          将int型5推送至栈顶
    0x09    lconst_0          将long型0推送至栈顶
    0x0a    lconst_1          将long型1推送至栈顶
    0x0b    fconst_0          将float型0推送至栈顶
    0x0c    fconst_1          将float型1推送至栈顶
    0x0d    fconst_2          将float型2推送至栈顶
    0x0e    dconst_0          将double型0推送至栈顶
    0x0f    dconst_1          将double型1推送至栈顶
    0x10    bipush            将单字节的常量值(-128~127)推送至栈顶
    0x11    sipush            将一个短整型常量值(-32768~32767)推送至栈顶
    0x12    ldc               将int, float或String型常量值从常量池中推送至栈顶
    0x13    ldc_w             将int, float或String型常量值从常量池中推送至栈顶(宽索引)
    0x14    ldc2_w            将long或double型常量值从常量池中推送至栈顶(宽索引)
    0x15    iload             将指定的int型本地变量
    0x16    lload             将指定的long型本地变量
    0x17    fload             将指定的float型本地变量
    0x18    dload             将指定的double型本地变量
    0x19    aload             将指定的引用类型本地变量
    0x1a    iload_0           将第一个int型本地变量
    0x1b    iload_1           将第二个int型本地变量
    0x1c    iload_2           将第三个int型本地变量
    0x1d    iload_3           将第四个int型本地变量
    0x1e    lload_0           将第一个long型本地变量
    0x1f    lload_1           将第二个long型本地变量
    0x20    lload_2           将第三个long型本地变量
    0x21    lload_3           将第四个long型本地变量
    0x22    fload_0           将第一个float型本地变量
    0x23    fload_1           将第二个float型本地变量
    0x24    fload_2           将第三个float型本地变量
    0x25    fload_3           将第四个float型本地变量
    0x26    dload_0           将第一个double型本地变量
    0x27    dload_1           将第二个double型本地变量
    0x28    dload_2           将第三个double型本地变量
    0x29    dload_3           将第四个do le型本地变量
    0x2a    aload_0           将第一个引用类型本地变量
    0x2b    aload_1           将第二个引用类型本地变量
    0x2c    aload_2           将第三个引用类型本地变量
    0x2d    aload_3           将第四个引用类型本地变量
    0x2e    iaload            将int型数组指定索引的值推送至栈顶
    0x2f    laload            将long型数组指定索引的值推送至栈顶
    0x30    faload            将float型数组指定索引的值推送至栈顶
    0x31    daload            将double型数组指定索引的值推送至栈顶
    0x32    aaload            将引用型数组指定索引的值推送至栈顶
    0x33    baload            将boolean或byte型数组指定索引的值推送至栈顶
    0x34    caload            将char型数组指定索引的值推送至栈顶
    0x35    saload            将short型数组指定索引的值推送至栈顶
    0x36    istore            将栈顶int型数值存入指定本地变量
    0x37    lstore            将栈顶long型数值存入指定本地变量
    0x38    fstore            将栈顶float型数值存入指定本地变量
    0x39    dstore            将栈顶double型数值存入指定本地变量
    0x3a    astore            将栈顶引用型数值存入指定本地变量
    0x3b    istore_0          将栈顶int型数值存入第一个本地变量
    0x3c    istore_1          将栈顶int型数值存入第二个本地变量
    0x3d    istore_2          将栈顶int型数值存入第三个本地变量
    0x3e    istore_3          将栈顶int型数值存入第四个本地变量
    0x3f    lstore_0          将栈顶long型数值存入第一个本地变量
    0x40    lstore_1          将栈顶long型数值存入第二个本地变量
    0x41    lstore_2          将栈顶long型数值存入第三个本地变量
    0x42    lstore_3          将栈顶long型数值存入第四个本地变量
    0x43    fstore_0          将栈顶float型数值存入第一个本地变量
    0x44    fstore_1          将栈顶float型数值存入第二个本地变量
    0x45    fstore_2          将栈顶float型数值存入第三个本地变量
    0x46    fstore_3          将栈顶float型数值存入第四个本地变量
    0x47    dstore_0          将栈顶double型数值存入第一个本地变量
    0x48    dstore_1          将栈顶double型数值存入第二个本地变量
    0x49    dstore_2          将栈顶double型数值存入第三个本地变量
    0x4a    dstore_3          将栈顶double型数值存入第四个本地变量
    0x4b    astore_0          将栈顶引用型数值存入第一个本地变量
    0x4c    astore_1          将栈顶引用型数值存入第二个本地变量
    0x4d    astore_2          将栈顶引用型数值存入第三个本地变量
    0x4e    astore_3          将栈顶引用型数值存入第四个本地变量
    0x4f    iastore           将栈顶int型数值存入指定数组的指定索引位置
    0x50    lastore           将栈顶long型数值存入指定数组的指定索引位置
    0x51    fastore           将栈顶float型数值存入指定数组的指定索引位置
    0x52    dastore           将栈顶double型数值存入指定数组的指定索引位置
    0x53    aastore           将栈顶引用型数值存入指定数组的指定索引位置
    0x54    bastore           将栈顶boolean或byte型数值存入指定数组的指定索引位置
    0x55    castore           将栈顶char型数值存入指定数组的指定索引位置
    0x56    sastore           将栈顶short型数值存入指定数组的指定索引位置
    0x57    pop               将栈顶数值弹出 (数值不能是long或double类型的)
    0x58    pop2              将栈顶的一个(long或double类型的)或两个数值弹出(其它)
    0x59    dup               复制栈顶数值并将复制值压入栈顶
    0x5a    dup_x1            复制栈顶数值并将两个复制值压入栈顶
    0x5b    dup_x2            复制栈顶数值并将三个(或两个)复制值压入栈顶
    0x5c    dup2              复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
    0x5d    dup2_x1           dup_x1 指令的双倍版本
    0x5e    dup2_x2           dup_x2 指令的双倍版本
    0x5f    swap              将栈最顶端的两个数值互换(数值不能是long或double类型的)
    0x60    iadd              将栈顶两int型数值相加并将结果压入栈顶
    0x61    ladd              将栈顶两long型数值相加并将结果压入栈顶
    0x62    fadd              将栈顶两float型数值相加并将结果压入栈顶
    0x63    dadd              将栈顶两double型数值相加并将结果压入栈顶
    0x64    is                将栈顶两int型数值相减并将结果压入栈顶
    0x65    ls                将栈顶两long型数值相减并将结果压入栈顶
    0x66    fs                将栈顶两float型数值相减并将结果压入栈顶
    0x67    ds                将栈顶两double型数值相减并将结果压入栈顶
    0x68    imul              将栈顶两int型数值相乘并将结果压入栈顶
    0x69    lmul              将栈顶两long型数值相乘并将结果压入栈顶
    0x6a    fmul              将栈顶两float型数值相乘并将结果压入栈顶
    0x6b    dmul              将栈顶两double型数值相乘并将结果压入栈顶
    0x6c    idiv              将栈顶两int型数值相除并将结果压入栈顶
    0x6d    ldiv              将栈顶两long型数值相除并将结果压入栈顶
    0x6e    fdiv              将栈顶两float型数值相除并将结果压入栈顶
    0x6f    ddiv              将栈顶两double型数值相除并将结果压入栈顶
    0x70    irem              将栈顶两int型数值作取模运算并将结果压入栈顶
    0x71    lrem              将栈顶两long型数值作取模运算并将结果压入栈顶
    0x72    frem              将栈顶两float型数值作取模运算并将结果压入栈顶
    0x73    drem              将栈顶两double型数值作取模运算并将结果压入栈顶
    0x74    ineg              将栈顶int型数值取负并将结果压入栈顶
    0x75    lneg              将栈顶long型数值取负并将结果压入栈顶
    0x76    fneg              将栈顶float型数值取负并将结果压入栈顶
    0x77    dneg              将栈顶double型数值取负并将结果压入栈顶
    0x78    ishl              将int型数值左移位指定位数并将结果压入栈顶
    0x79    lshl              将long型数值左移位指定位数并将结果压入栈顶
    0x7a    ishr              将int型数值右(符号)移位指定位数并将结果压入栈顶
    0x7b    lshr              将long型数值右(符号)移位指定位数并将结果压入栈顶
    0x7c    iushr             将int型数值右(无符号)移位指定位数并将结果压入栈顶
    0x7d    lushr             将long型数值右(无符号)移位指定位数并将结果压入栈顶
    0x7e    iand              将栈顶两int型数值作“按位与”并将结果压入栈顶
    0x7f    land              将栈顶两long型数值作“按位与”并将结果压入栈顶
    0x80    ior               将栈顶两int型数值作“按位或”并将结果压入栈顶
    0x81    lor               将栈顶两long型数值作“按位或”并将结果压入栈顶
    0x82    ixor              将栈顶两int型数值作“按位异或”并将结果压入栈顶
    0x83    lxor              将栈顶两long型数值作“按位异或”并将结果压入栈顶
    0x84    iinc              将指定int型变量增加指定值(i++, i–, i+=2)
    0x85    i2l               将栈顶int型数值强制转换成long型数值并将结果压入栈顶
    0x86    i2f               将栈顶int型数值强制转换成float型数值并将结果压入栈顶
    0x87    i2d               将栈顶int型数值强制转换成double型数值并将结果压入栈顶
    0x88    l2i               将栈顶long型数值强制转换成int型数值并将结果压入栈顶
    0x89    l2f               将栈顶long型数值强制转换成float型数值并将结果压入栈顶
    0x8a    l2d               将栈顶long型数值强制转换成double型数值并将结果压入栈顶
    0x8b    f2i               将栈顶float型数值强制转换成int型数值并将结果压入栈顶
    0x8c    f2l               将栈顶float型数值强制转换成long型数值并将结果压入栈顶
    0x8d    f2d               将栈顶float型数值强制转换成do le型数值并将结果压入栈顶
    0x8e    d2i               将栈顶double型数值强制转换成int型数值并将结果压入栈顶
    0x8f    d2l               将栈顶double型数值强制转换成long型数值并将结果压入栈顶
    0x90    d2f               将栈顶double型数值强制转换成float型数值并将结果压入栈顶
    0x91    i2b               将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
    0x92    i2c               将栈顶int型数值强制转换成char型数值并将结果压入栈顶
    0x93    i2s               将栈顶int型数值强制转换成short型数值并将结果压入栈顶
    0x94    lcmp              比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
    0x95    fcmpl             比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
    0x96    fcmpg             比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
    0x97    dcmpl             比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
    0x98    dcmpg             比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
    0x99    ifeq              当栈顶int型数值等于0时跳转
    0x9a    ifne              当栈顶int型数值不等于0时跳转
    0x9b    iflt              当栈顶int型数值小于0时跳转
    0x9c    ifge              当栈顶int型数值大于等于0时跳转
    0x9d    ifgt              当栈顶int型数值大于0时跳转
    0x9e    ifle              当栈顶int型数值小于等于0时跳转
    0x9f    if_icmpeq         比较栈顶两int型数值大小,当结果等于0时跳转
    0xa0    if_icmpne         比较栈顶两int型数值大小,当结果不等于0时跳转
    0xa1    if_icmplt         比较栈顶两int型数值大小,当结果小于0时跳转
    0xa2    if_icmpge         比较栈顶两int型数值大小,当结果大于等于0时跳转
    0xa3    if_icmpgt         比较栈顶两int型数值大小,当结果大于0时跳转
    0xa4    if_icmple         比较栈顶两int型数值大小,当结果小于等于0时跳转
    0xa5    if_acmpeq         比较栈顶两引用型数值,当结果相等时跳转
    0xa6    if_acmpne         比较栈顶两引用型数值,当结果不相等时跳转
    0xa7    goto              无条件跳转
    0xa8    jsr               跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
    0xa9    ret               返回至本地变量
    0xaa    tableswitch       用于switch条件跳转,case值连续(可变长度指令)
    0xab    lookupswitch      用于switch条件跳转,case值不连续(可变长度指令)
    0xac    ireturn           从当前方法返回int
    0xad    lreturn           从当前方法返回long
    0xae    freturn           从当前方法返回float
    0xaf    dreturn           从当前方法返回double
    0xb0    areturn           从当前方法返回对象引用
    0xb1    return            从当前方法返回void
    0xb2    getstatic         获取指定类的静态域,并将其值压入栈顶
    0xb3    putstatic         为指定的类的静态域赋值
    0xb4    getfield          获取指定类的实例域,并将其值压入栈顶
    0xb5    putfield          为指定的类的实例域赋值
    0xb6    invokevirtual     调用实例方法
    0xb7    invokespecial     调用超类构造方法,实例初始化方法,私有方法
    0xb8    invokestatic      调用静态方法
    0xb9    invokeinterface   调用接口方法
    0xba    –                 无此指令
    0xbb    new               创建一个对象,并将其引用值压入栈顶
    0xbc    newarray          创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
    0xbd    anewarray         创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
    0xbe    arraylength       获得数组的长度值并压入栈顶
    0xbf    athrow            将栈顶的异常抛出
    0xc0    checkcast         检验类型转换,检验未通过将抛出ClassCastException
    0xc1    instanceof        检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
    0xc2    monitorenter      获得对象的锁,用于同步方法或同步块
    0xc3    monitorexit       释放对象的锁,用于同步方法或同步块
    0xc4    wide              使用附加字节扩展局部变量索引(iinc指令特殊)
    0xc5    multianewarray    创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
    0xc6    ifnull            为null时跳转
    0xc7    ifnonnull         不为null时跳转
    0xc8    goto_w            无条件跳转(宽索引)
    0xc9    jsr_w             跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶
    
    Exceptions_attribute

    当一个方法抛出异常(Exception)或错误(Error)时,这个方法的method_info将保存此属性

    Exceptions_attribute {
        u2 attribute_name_index;//属性名字在常量池中的索引
        u4 attribute_length;//属性字节长度,从下一个字节开始算
        u2 number_of_exceptions;//异常数量
        u2 exception_index_table[number_of_exceptions];//每个异常在常量池中的索引值
    }
    
    SourceFile_attribute

    此属性属于类属性,就是ClassFile文件格式中的最后一项。表示Class对应的源码文件名。是指向常量池的一个索引值。

    SourceFile_attribute {
    u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
    u4 attribute_length;//该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
    u2 sourcefile_index;//表示Class对应的源码文件名
    }
    
    LocalVariableTable_attribute

    这个属性是包含在Code里的属性,用来描述一个方法的局部变量相关信息。比如变量作用域(start,length)、位置(slot)、名字(Name)、类型(Signature)

    LocalVariableTable_attribute {
        u2 attribute_name_index;//属性名字在常量池中的索引
        u4 attribute_length;//属性字节长度,从下一个字节开始算
        u2 local_variable_table_length;//局部变量数量
        {   u2 start_pc;//局部变量作用的开始指令位置
            u2 length;//局部变量作用域的长度
            u2 name_index;//局部变量名字,指向常量池的索引
            u2 descriptor_index;//局部变量描述(类型),指向常量池的索引
            u2 index;//局部变量在局部变量表中的位置
        } local_variable_table[local_variable_table_length];//每个局部变量
    }
    
    LineNumberTable_attribute

    这个属性是包含在Code里的属性,表明字节码指令和和源码的对应关系。即指令开始位置(start_pc)对应源码行号(line_number)。不一定是一一对应,可能多对一,即多条指令对应一行源码。用于Java调试。

    LineNumberTable_attribute {
    u2 attribute_name_index;//属性名字在常量池中的索引
    u4 attribute_length;//属性字节长度,从下一个字节开始算
    u2 line_number_table_length;//数量,有多少条对应关系
    {u2 start_pc;//指令开始位置
    u2 line_number;//对应源码行号
    }line_number_table [line_number_table_length];//每条对应关系
    }
    

    上面介绍了5中常见的属性。
    我们接着分析Person.class。
    由上面的属性格式可知,所有属性前2个字节表示属性的名字在常量池中的索引。然后接下来的4个字节表示属性内容的长度。Person.class中如下:


    第一个方法(构造方法)的Code属性.png 第一个方法javap -v反解析内容的Code属性.png
    00 0B//属性名字,转为10进制是:11,指向常量池中第11个常量,即 Code。表明是一个Code属性
    00 00 00 2F//属性内容有多少字节,2/*16+15=47。表示接下来47个字节描述Code属性信息。
    

    通过上面的分析,发现是一个Code属性,且其内容有47个字节。那我们按照Code属性的格式分析接下来的47个字节:

    00 01//操作数栈深度的最大值为1
    00 01//局部变量表所需的存储空间为1个Slot;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位,一般来讲就是1byte。
    00 00 00 05//方法指令所需要的字节数。并不代表有5条指令。它包含指令和指令的参数。本例中就只有3条指令,其中一条指令(invokespecial) 有一个参数 这个参数是2个字节。加起来就是5个字节。
    2A//aload_0 将第一个引用类型局部变量推送至栈顶 根据后面的LocalVariableTable可知:第一个引用类型局部变量是this
    B7//invokespecial 调用超类构造方法,实例初始化方法,私有方法
    00 0C//是invokespecial 后的参数 指向常量池,转化为10进制是12 即:第12个常量,查下常量池可得:12->Methodref ->3,13-> java/lang/Object."<init>":()V  。表示执行父类构造方法构造方法
    B1//return  从当前方法返回void 
    00 00  //exception_table_length 表示没有异常
    00 02 //attributes_count 表示Code属性里还有2个内部属性。接下来就是2个内部属性的具体内容
    00 0E//第一个内部属性的名字,指向常量池的索引 转化为10进制是:14 ,即第14个常量:LineNumberTable
    00 00 00 06////属性字节长度为6,从下一个字节开始算
    00 01//其行号表长度为1,即有1个行号表
    00 00//start_pc 字节码行号为0,即 aload_0 代表加载this
    00 03//line_number源码行号为3.即 public class Person{
    00 0F//表示第二个内部属性的名字,转化为10进制是15,即第15个常量:LocalVariableTable 局部变量表属性
    00 00 00 0C//属性字节长度为12,从下一个字节开始算
    00 01//表示有一个局部变量
    00 00//start_pc 表示局部变量开始作用的字节码行号是0号,即aload_0 
    00 05//length,表示这个局部变量的作用域长度是5
    00 10//name_index,局部变量名字,指向常量池的索引,转为10进制是16,即第16个常量:this
    00 11//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,转为10进制是17,即第17个常量:Lhilbert/Person
    00 00//index,局部变量this在局部变量表中位置。即第0个位置。
    

    到此第一个方法分析完毕!

    第2个方法

    Person.class如下:


    第二个方法-getName().png
    第二个方法.png
    00 01//access_flags=ACC_PUBLIC 表示public类型方法
    00 12//name_index,方法名字索引,转化为10进制是:18,指向常量池为:getName
    00 13//descriptor_index,方法描述(参数和返回值)索引,转化为10进制是:19,指向常量池为:()Ljava/lang/String; 表示没有参数,返回值类型是String。
    00 01//方法属性数量为1
    00 0B//第一个方法属性的名字的在常量池中的索引,0B->11->Code。表示是个Code属性
    00 00 00 2F//表示Code属性内容的长度为2*16+15=47个字节
    00 01//操作数栈深度的最大值为1
    00 01//局部变量表所需的存储空间为1个Slot;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位,一般来讲就是1byte。
    00 00 00 05//指令内容所占字节数为5个字节
    2A//aload_0  将第一个引用类型局部变量推送至栈顶 根据后面的LocalVariableTable可知:第一个引用类型局部变量是this
    B4//getfield 获取指定类的实例域,并将其值压入栈顶,下面2个字节是它的参数
    00 14// 转化为10进制是20,即:20->Fieldref->#1.#21->hilbert/Person.name:Ljava/lang/Stri
    ng;(对应源码即name字段,加上上面的aload_0 即是this.name)
    B0//areturn 从当前方法返回对象引用 即返回对应源码:return this.name;
    00 00//表示没有异常
    00 02//表示有2个内部属性
    00 0E//第一个内部属性的名字,指向常量池的索引 转化为10进制是:14 ,即第14个常量:LineNumberTable
    00 00 00 06//表示接下来6个字节为LineNumberTable属性的内容
    00 01//其行号表长度为1,即有1个行号表
    00 00//start_pc 字节码行号为0,即 aload_0 代表加载this
    00 09//line_number源码行号为9.即 return name;
    00 0F//表示第2个内部属性的名字,转化为10进制是15,即第15个常量:LocalVariableTable 局部变量表属性
    00 00 00 0C//属性字节长度为12,从下一个字节开始算
    00 01//表示有一个局部变量
    00 00//start_pc 表示局部变量开始作用的字节码行号是0号,即aload_0 
    00 05//length,表示这个局部变量的作用域长度是5
    00 10//name_index,局部变量名字,指向常量池的索引,转为10进制是16,即第16个常量:this
    00 11//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,转为10进制是17,即第17个常量:Lhilbert/Person
    00 00//index,局部变量this在局部变量表中位置。即第0个位置。
    

    第三个方法

    Person.class如下:


    第三个方法-setName(String name).png 第三个方法.png
    00 01//access_flags=ACC_PUBLIC 表示public类型方法
    00 16//name_index,方法名字索引,转化为10进制是:22,指向常量池为:setName
    00 17//descriptor_index,方法描述(参数和返回值)索引,转化为10进制是:23,指向常量池为:(Ljava/lang/String;)V; 表示1个参数,类型为String,返回值类类Void。
    00 01//方法属性数量为1
    00 0B//第一个方法属性的名字的在常量池中的索引,0B->11->Code。表示是个Code属性
    00 00 00 3E//3*16+14=62,表示接下来62个字节是Code属性的内容
    00 02//操作数栈深度的最大值为2
    00 02//局部变量表所需的存储空间为2个Slot;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位,一般来讲就是1byte。
    00 00 00 06//指令内容所占字节数为6个字节
    2A//aload_0  将第一个引用类型局部变量推送至栈顶 根据后面的LocalVariableTable可知:第一个引用类型局部变量是this
    2B//aload_1 将第二个引用类型本地变量推送至栈顶 即:name
    B5//putfield 为指定的类的实例域赋值 即为name赋值
    00 14// 赋值参数 0x0014->#20->Fieldref->#1.#21->hilbert/Person.name:Ljava/lang/String;
    B1//return 从当前方法返回void
    00 00//表示没有异常
    00 02//表示有2个内部属性
    00 0E//第一个内部属性的名字,指向常量池的索引 转化为10进制是:14 ,即第14个常量:LineNumberTable
    00 00 00 0A//表示接下来10个字节为LineNumberTable属性的内容
    00 02//其行号表长度为2,即有2个行号表
    00 00//第一个行号的start_pc 字节码行号为0,即 aload_0 代表加载this
    00 0D//line_number源码行号为13.即 "this.name = name";
    00 05//第二个行号的start_pc 字节码行号为5,即 return 代表方法返回void
    00 0E//line_number源码行号为14。即"}";
    00 0F//表示第2个内部属性的名字,转化为10进制是15,即第15个常量:LocalVariableTable 局部变量表属性
    00 00 00 16//属性字节长度为22,从下一个字节开始算
    00 02//表示有2个局部变量
    00 00//第1个局部变量(this)的start_pc, 表示局部变量开始作用的字节码行号是0号,即aload_0 
    00 06//length,表示这个局部变量的作用域长度是6
    00 10//name_index,局部变量名字,指向常量池的索引,转为10进制是16,即第16个常量:this
    00 11//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,转为10进制是17,即第17个常量:Lhilbert/Person
    00 00//index,第1个局部变量this在局部变量表中位置。即第0个位置。
    00 00//第2个局部变量(name)的start_pc ,表示局部变量开始作用的字节码行号是0号,即aload_0 
    00 06//length,表示这个局部变量的作用域长度是6
    00 05//name_index,局部变量名字,指向常量池的索引,即:name
    00 06//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,即:Ljava/lang/String;
    00 01//index,第2个局部变量name在局部变量表中位置。即第1个位置。
    

    第4,第5个方法

    第4,第5个方法和第2,第3个方法非常相似,就不具体分析了。
    下面是它们的Person.class


    第4个方法-getNumber().png
    第4个方法.png
    第5个方法-setNumber(int number).png
    第5个方法.png

    到此,所有方法分析完毕!

    15、u2 attributes_count

    attributes_count 类的属性数量

    00 01//表示有1个类属性
    

    16、attribute_info attributes[attributes_count]

    attribute_info 具体的类属性
    这个是个SourceFile属性,上面有介绍,在这儿再写一遍它的格式:

    SourceFile_attribute {
    u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
    u4 attribute_length;//该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
    u2 sourcefile_index;//表示Class对应的源码文件名
    }
    

    Person.class:

    00 1E//属性名字索引,0x1E->#30->SourceFile,
    00 00 00 02////该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
    00 1F//属性内容索引,0x1F->#31->Person.java,即是。源代码文件名
    

    到此,一个完整的Class文件分析完毕!

    相关文章

      网友评论

          本文标题:完整分析Class字节码

          本文链接:https://www.haomeiwen.com/subject/nctfxhtx.html