Java class文件結構(實例篇)

?在《JAVA CLASS的文件結構(規范篇)》中我們基本完整的描述了class的文件結構規范,這一篇我們以一個實際的例子,來逐行分析class的二進制格式,從而深入了解class文件結構。
?分析代碼如下:

package com.sunny.jdk.classfile;

/**
 * <Description> <br>
 *
 * @author Sunny<br>
 * @version 1.0<br>
 * @taskId: <br>
 * @createDate 2018/09/17 15:52 <br>
 * @see com.sunny.jdk.classfile <br>
 */
public class TestClass {
    private int m;

    public int inc() {
        return m + 1;
    }
}

?class文件使用winhex打開的二進制結構如下:



下面我們就根據前篇所說的Class的文件結構來解析以下上圖中字節流:

1. 魔數

?從Class的文件結構我們知道,剛開始的4個字節是魔數,上圖中從地址000000000-000000003的內容就是魔數,從上圖可知Class的文件的魔數是0xCAFEBABE

2. 主副版本號

?接下來的4個字節是主副版本號,有上圖可知從000000004-000000005對應的是0x0000,因此Class的minor_version 為0x0000,從000000006-000000007對應的內容為0x0034,因此Class文件的major_version版本為 0x0034,這正好就是jdk1.8編譯后的Class對應的主次版本。

3. 常量池數量

?接下來的2個字節從000000008-000000009表示常量池的數量,由上圖可以知道其值為0x0016,十進制為22個,但是對于常量池的數量 需要明確一點,常量池的數量是constant_pool_count-1,為什么減一,是因為索引0表示class中的數據項不引用任何常量池中的常 量。所以常量池的個數應該是21個;

4. 常量池

?前篇已經說過了常量池中有不同類型的常量,下面就來看看TestClass.class的第一個常量,每個常量都有一個u1類型的tag標識來表示 常量的類型,上圖中00000000A處的內容為0x0A,轉換成二級制是10,有上面的關于常量類型的描述可知tag10的常量是Constant_Methodref_info,而Constant_Methodref_info的結構如下:

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

?其中tag對應上圖位置00000000A處的內容為0x0Aclass_index是接下來兩個字節的數據,對應的上圖位置的00000000B-00000000C,其值為0x0004,也就是說指向第四個常量;name_and_type_index指向常量池中類型為CONSTANT_NameAndType_info常量。從上圖可以看出name_and_type_index的值為0x0012,對應上圖的位置是00000000D-00000000E表示指向常量池中的第18個常量。接下來我們可以找到常量池中所有的常量:

  • 第2個常量:
    tag對應00000000F位置的內容是0x09,表示CONSTANT_Fieldref_info,其結構如下:
CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

?其中tag對應上圖位置00000000F處的內容為0x09;class_index是接下來兩個字節的數據,對應的上圖位置的000000100-000000101,其值為0x0003,也就是說指向第3個常量;name_and_type_index指向常量池中類型為CONSTANT_NameAndType_info常量。從上圖可以看出name_and_type_index的值為0x0013,對應上圖的位置是000000102-000000103表示指向常量池中的第19個常量。

  • 第3個常量:
    tag對應000000104位置的內容是0x07,表示CONSTANT_Class_info,其結構如下:
CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

?其中tag對應上圖位置000000104處的內容為0x07name_index是接下來兩個字節的數據,對應的上圖位置的000000105-000000106,其值為0x0014,也就是說指向第20個常量;

  • 第4個常量:
    tag對應000000107位置的內容是0x07,表示CONSTANT_Class_info,其結構如下:
CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

?其中tag對應上圖位置000000107處的內容為0x07;name_index是接下來兩個字節的數據,對應的上圖位置的000000108-000000109,其值為0x0015,也就是說指向第21個常量;

  • 第5個常量:
    tag對應00000010A位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置00000010A處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的00000010B-00000010C,其值為0x0001,也就是length為1;緊接著后面length個字節表示的數據即為該數據,因為length=1,所以00000010D處的數據0x6D表示的即為該數據,轉換成ASCII即為m

  • 第6個常量:
    tag對應00000010E位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置00000010E處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的00000010F-000000200,其值為0x0001,也就是length為1;緊接著后面length個字節表示的數據即為該數據,因為length=1,所以000000201處的數據0x49表示的即為該數據,轉換成ASCII即為I

  • 第7個常量:
    tag對應000000202位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000202處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000203-000000204,其值為0x0006,也就是length為6;緊接著后面length個字節表示的數據即為該數據,因為length=6,所以000000205-00000020A處的數據0x3C, 0x69,0x6E,0x69, 0x74, 0x3E表示的即為該數據,轉換成ASCII即為<init>

  • 第8個常量:
    tag對應00000020B位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置00000020B處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的00000020C-00000020D,其值為0x0003,也就是length為3;緊接著后面length個字節表示的數據即為該數據,因為length=3,所以00000020E-000000300處的數據0x28, 0x29,0x56表示的即為該數據,轉換成ASCII即為()V

  • 第9個常量:
    tag對應000000301位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000301處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000302-000000303,其值為0x0004,也就是length為4;緊接著后面length個字節表示的數據即為該數據,因為length=4,所以000000304-000000307處的數據0x43, 0x6F,0x64,0x65表示的即為該數據,轉換成ASCII即為Code

  • 第10個常量:
    tag對應000000308位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000308處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000309-00000030A,其值為0x000F,也就是length為15;緊接著后面length個字節表示的數據即為該數據,因為length=15,所以00000030B-000000409處的數據0x4C, 0x69,0x6E,0x65,0x4E,0x75,0x6D,0x62,0x65,0x72,0x54,0x61,0x62,0x6C,0x65表示的即為該數據,轉換成ASCII即為LineNumberTable

  • 第11個常量:
    tag對應00000040A位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置00000040A處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000409-00000040A,其值為0x0012,也就是length為18;緊接著后面length個字節表示的數據即為該數據,因為length=18,所以00000040B-00000050E處的數據0x4C,0x6F,0x63,0x61,0x6C,0x56,0x61,0x72,0x69,0x61,0x62,0x6C,0x65,0x54,0x61,0x62,0x6C,0x65表示的即為該數據,轉換成ASCII即為LocalVariableTable

  • 第12個常量:
    tag對應00000050F位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置00000050F處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000600-000000601,其值為0x0004,也就是length為4;緊接著后面length個字節表示的數據即為該數據,因為length=4,所以000000602-000000605處的數據0x74,0x68,0x69,0x73表示的即為該數據,轉換成ASCII即為this

  • 第13個常量:
    tag對應000000606位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000606處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000607-000000608,其值為0x0023,也就是length為35;緊接著后面length個字節表示的數據即為該數據,因為length=35,所以000000609-00000080B處的數據如圖:


,轉換成ASCII即為Lcom/sunny/jdk/classfile/TestClass;

  • 第14個常量:
    tag對應00000080C位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置00000080C處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的00000080D-00000080E,其值為0x0003,也就是length為3;緊接著后面length個字節表示的數據即為該數據,因為length=3,所以00000080F-000000901處的數據0x69,0x6E,0x63表示的即為該數據,轉換成ASCII即為inc

  • 第15個常量:
    tag對應000000902位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000902處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000903-000000904,其值為0x0003,也就是length為3;緊接著后面length個字節表示的數據即為該數據,因為length=3,所以000000905-000000907處的數據0x28,0x29,0x49表示的即為該數據,轉換成ASCII即為()I

  • 第16個常量:
    tag對應000000908位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000908處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000909-00000090A,其值為0x000A,也就是length為10;緊接著后面length個字節表示的數據即為該數據,因為length=10,所以00000090B-000000A04處的數據如圖:


,轉換成ASCII即為SourceFile

  • 第17個常量:
    tag對應000000A05位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000A05處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000A06-000000A07,其值為0x000E,也就是length為14;緊接著后面length個字節表示的數據即為該數據,因為length=14,所以000000A08-000000B05處的數據如圖:


,轉換成ASCII即為TestClass.java

  • 第18個常量:
    tag對應000000B06位置的內容是0x0C,表示CONSTANT_NameAndType_info,其結構如下:
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index; 
    u2 descriptor_index;
}

?其中tag對應上圖位置000000B06處的內容為0x0C;name_index是接下來兩個字節的數據,對應的上圖位置的000000B07-000000B08,其值為0x0007,也就是說指向第7個常量;descriptor_index是接下來的2個字節,從上圖可以看出descriptor_index的值為0x0008,對應上圖的位置是000000B09-000000B0A表示指向常量池中的第8個常量。

  • 第19個常量:
    tag對應000000B0B位置的內容是0x0C,表示CONSTANT_NameAndType_info,其結構如下:
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index; 
    u2 descriptor_index;
}

?其中tag對應上圖位置000000B0B處的內容為0x0C;name_index是接下來兩個字節的數據,對應的上圖位置的000000B0C-000000B0D,其值為0x0005,也就是說指向第5個常量;descriptor_index是接下來的2個字節,從上圖可以看出descriptor_index的值為0x0006,對應上圖的位置是000000B0E-000000B0F表示指向常量池中的第6個常量。

  • 第20個常量:
    tag對應000000C00位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000C00處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000C01-000000C02,其值為0x0021,也就是length為33;緊接著后面length個字節表示的數據即為該數據,因為length=33,所以000000C03-000000E03處的數據如圖:


,轉換成ASCII即為com/sunny/jdk/classfile/TestClass

  • 第21個常量:
    tag對應000000E04位置的內容是0x01,表示CONSTANT_Utf8_info,其結構如下:
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

?其中tag對應上圖位置000000E04處的內容為0x01;length是接下來兩個字節的數據,對應的上圖位置的000000E05-000000E06,其值為0x0010,也就是length為16;緊接著后面length個字節表示的數據即為該數據,因為length=16,所以000000E07-000000F06處的數據如圖:


,轉換成ASCII即為java/lang/Object

?這樣常量池的21個常量全部分析完畢,不過JDK提供了一個方便的工具可以讓我們查看常量池中所包含的常量。通過javap -verbose TestClass 即可得到所有常量池中的常量,如下所示:

C:\ProgramFiles\Java\jdk1.8.0_144\bin>javap -verbose E:\workspace\workspace_java_tool\workspace_sunny_project\java-honey-collection\target\classes\com\sunny\jdk\classfile\TestClass.class
Classfile /E:/workspace/workspace_java_tool/workspace_sunny_project/java-honey-collection/target/classes/com/sunny/jdk/classfile/TestClass.class
  Last modified 2018-10-23; size 401 bytes
  MD5 checksum b02768ba5213ac8bcfdcd0865e8d7374
  Compiled from "TestClass.java"
public class com.sunny.jdk.classfile.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#18         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#19         // com/sunny/jdk/classfile/TestClass.m:I
   #3 = Class              #20            // com/sunny/jdk/classfile/TestClass
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/sunny/jdk/classfile/TestClass;
  #14 = Utf8               inc
  #15 = Utf8               ()I
  #16 = Utf8               SourceFile
  #17 = Utf8               TestClass.java
  #18 = NameAndType        #7:#8          // "<init>":()V
  #19 = NameAndType        #5:#6          // m:I
  #20 = Utf8               com/sunny/jdk/classfile/TestClass
  #21 = Utf8               java/lang/Object
{
  public com.sunny.jdk.classfile.TestClass();
    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 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/sunny/jdk/classfile/TestClass;

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 16: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/sunny/jdk/classfile/TestClass;
}
SourceFile: "TestClass.java"

?由此常量池已經全部分析完畢,接下來我們即將分access_flags。

5. u2 access_flags

?access_flags表示類或者接口方面的訪問信息,比如Class表示的是類還是接口,是否為public,static,final等。具體訪問標示的含義之前已經說過 了,下面我們就來看看TestClass的訪問標示。Class的訪問標示是000000F07-000000F08,期值為0x0021,根據前面說的 各種訪問標示的標志位,我們可以知道:0x0021=0x0001|0x0020 也即ACC_PUBLIC 和 ACC_SUPER為真,其中ACC_PUBLIC大家好理解,ACC_SUPER是jdk1.0.2之后編譯的類都會帶有的標志。

6. u2 this_class 和 u2 super_class

?this_class表示當前類的索引值,super_class 表示當前類的父類的索引值;索引值所指向的常量池中類型為CONSTANT_Class_info的常量;例子中類索引值的是000000F09-000000F0A所表示的值0x0003,也即常量池的第3個常量,從而可知,this_class的全限定名為com/sunny/jdk/classfile/TestClass;同理,super_class是000000F0B-000000F0C所表示的值0x0004,也即常量池的第4個常量,從而可知,super_class的全限定名為java/lang/Object

6. u2 interfaces_count和 interfaces[interfaces_count]

?interfaces_count和 interfaces[interfaces_count]表示接口數量以及具體的每一個接口;TestClass的接口數量是000000F0D-000000F0E所表示的值0x0000,也即沒有實現任何接口;

7. u2 fields_count 和 field_info

? fields_count表示類中field_info表的數量,而field_info表示類的實例變量和類變量,這里需要注意的是 field_info不包含從父類繼承過來的字段,field_info的結構如下:

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

?例子中接口后面緊接著兩個字節000000F0F-000001000中的0x0001即為字段的個數,從而可知字段的個數為1,與代碼樣例相符;后面即為字段的詳細信息;
?access_flags 是000001001-000001002中0x0002,對照上一篇的字段修飾符表格可知字段的修飾符為ACC_PRIVATE(0x0002);name_index是000001003-000001004表達的0x0005,表示常量池中的第5個常量,也即是m,表示字段名為m;descriptor_index是000001005-000001006表達的0x0006,表示常量池中的第6個常量,也I,表示int類型;attributes_count是000001007-000001008位置的0x0000,表示沒有屬性;沒有屬性的話,后面就是methods_count和method_info;

8. u2 methods_count 和 method_info

?其中methods_count表示方法的數量,而method_info表示的方法表,其中方法表的結構如下所示:

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

?methods_count是000001009-00000100A位置的0x0002,表示有2個method;

  • 第1個方法:
    access_flags是00000100B-00000100C位置的0x0001,表示ACC_PUBLIC(0x0001);name_index是00000100D-00000100E位置的0x0007,表示常量池的第7個常量,也即方法名為<init>;descriptor_index是00000100F-000001100位置的0x0008,也即常量池的第8個常量,也即()V;attributes_count是000001101-000001102位置的0x0001,表示有1個屬性,后面緊跟的就是屬性,屬性通用結構如下,不同的屬性結構也不完全一樣:
attribute_info {
    u2 attribute_name_index;   //屬性名索引
    u4 attribute_length;       //屬性長度
    u1 info[attribute_length]; //屬性的具體內容
}
  • <init>方法的第1個屬性:
    attribute_name_index是000001103-000001104位置的0x0009,表示常量池的第9個常量,也即Code,表示該屬性是Code屬性,Code的屬性結構如下:
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;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc; 
        u2 catch_type; 
    } exception_table[exception_table_length]; 
    u2 attributes_count;     
    attribute_info attributes[attributes_count]; 
}

?attribute_length(attribute_length不包括attribute_name_index和attribute_length的6個字節的長度)為000001105-000001108位置的0x0000002F,表示屬性長度為47;max_stack(max_stack表示最大棧深度,虛擬機在運行時根據這個值來分配棧幀中操作數的深度)為000001109-00000110A位置的0x0001,表示最大棧幀深度為1;max_locals(max_locals代表了局部變量表的存儲空間)為00000110B-00000110C位置的0x0001,表示局部變量的存儲空間為1;code_length(code_length代表了字節碼指令的數量,而code表示的是字節碼指令,從Code屬性結構中可以知道code的類型為u1,一個u1類型的取值為0x00-0xFF,對應的十進制為0-255,目前虛擬機規范已經定義了200多條指令)為00000110D-000001200位置的0x00000005,表示Code字節碼指令的數量為5;后面的5個字節即為字節碼,位置000001201-000001205的數據為“2A B7 00 01 B1”,翻譯如下:

  • 讀入2A,查虛擬機規范1.8知道指令操作碼0x2A對應的指令為aload_0,這個指令的含義是講第0個Slot中為reference類型的本地變量推送到操作數棧頂;
  • 讀入B7,查表得0xB7對應的指令為invokespecial,這條指令的作用是以棧頂的reference類型的數據所指向的對象作為方法接受者,調用此對象的實例構造器方法、private方法或者它的父類的方法。這個方法有一個u2類型的參數說明具體調用哪一個方法,它指向常量池中的一個CONSTANT_Methodref_info類型常量,即此方法的符號引用。
  • 讀入00 01,這是invokespecial的參數,查常量池得0x0001對應的常量為實例構造器<init>方法的符號引用;
  • 讀入B1,查表得0xB1對應的指令為return,含義是返回此方法,并且返回值為void,這條指令執行后,當前方法結束;

?exception_table_length為000001206-000001207位置的0x0000,表示exception_table_length為0,后面沒有exception相關數據;緊接著兩個字節表示attributes_count,即位置000001208-0000012090x0002,表示有2個attribute;分析Code屬性結構中的兩個屬性如下:

  • 第1個屬性:
    ?attribute_name_index為00000120A-00000120B位置的0x000A,表示引用常量池的第10個常量,也即LineNumberTable,LineNumberTable的屬性結構如下:
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];
}

?attribute_length為00000120C-00000120F位置的0x00000006,表示屬性長度為6;line_number_table_length為000001300-000001301位置的0x0001,也即擁有一個line_number_table;后面緊跟著line_number_table,其中start_pc為000001302-000001303位置的0x0000,line_number為000001304-000001305位置0x000C;

  • 第2個屬性:
    ?attribute_name_index為000001306-000001307位置的0x000B,表示引用常量池的第11個常量,也即LocalVariableTable,LocalVariableTable的屬性結構如下:
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];
}

?attribute_length為000001308-00000130B位置的0x0000000C,表示屬性長度為12;local_variable_table_length為00000130C-00000130D位置的0x0001,表示local_variable_table的長度為1;緊接著local_variable_table的分析,其中start_pc為00000130E-00000130F位置的0x0000,length為000001400-000001401位置的0x0005,name_index為000001402-000001403位置的0x000C,表示name為常量池的第12個屬性,也即this,descriptor_index為000001404-000001405位置的0x000D,也即常量池的第13個屬性,也即Lcom/sunny/jdk/classfile/TestClass;,index為000001406-000001407位置的0x0000

  • 第2個方法:

?access_flags為000001408-000001409位置的0x0001,表示ACC_PUBLIC(0x0001);name_index是00000140A-00000140B位置的0x000E,表示常量池的第14個常量,也即方法名為inc;descriptor_index是00000140C-00000140D位置的0x000F,也即常量池的第15個常量,也即()I,表示返回值為int;attributes_count是00000140E-00000140F位置的0x0001,表示有inc方法有1個屬性,后面緊跟的就是屬性分析; *inc方法的第1個屬性: attribute_name_index是000001500-000001501位置的0x0009,表示常量池的第9個常量,也即Code`,表示該屬性是Code屬性,Code的屬性結構如下:

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;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc; 
        u2 catch_type; 
    } exception_table[exception_table_length]; 
    u2 attributes_count;     
    attribute_info attributes[attributes_count]; 
}

?attribute_length為000001502-000001505位置的0x00000031,表示屬性長度為49;max_stack(max_stack表示最大棧深度,虛擬機在運行時根據這個值來分配棧幀中操作數的深度)為000001506-000001507位置的0x0002,表示最大棧幀深度為2;max_locals(max_locals代表了局部變量表的存儲空間)為000001508-000001509位置的0x0001,表示局部變量的存儲空間為1;code_length(code_length代表了字節碼指令的數量,而code表示的是字節碼指令,從Code屬性結構中可以知道code的類型為u1,一個u1類型的取值為0x00-0xFF,對應的十進制為0-255,目前虛擬機規范已經定義了200多條指令)為00000150A-00000150D位置的0x00000007,表示Code字節碼指令的數量為7;后面的7個字節即為字節碼,位置00000150E-000001604的數據為“2A B4 00 02 04 60 AC”,翻譯如下:

  • 讀入2A,查虛擬機規范1.8知道指令操作碼0x2A對應的指令為aload_0,這個指令的含義是講第0個Slot中為reference類型的本地變量推送到操作數棧頂;

  • 讀入B4,查表得0xB4對應的指令為getfield,這條指令的作用是獲取指定類的實例字段,并將其壓入棧頂;這個方法有一個u2類型的參數說明具體調用哪一個類的什么屬性,它指向常量池中的一個CONSTANT_Fieldref_info類型常量,即此屬性的符號引用。

  • 讀入00 02,這是getfield的參數,查常量池得0x0002對應的常量為com/sunny/jdk/classfile/TestClass.m:I

  • 讀入04,查表得0x04對應的指令為iconst_1,含義是將int類型2推送至棧頂;

  • 讀入60,查表得0x60對應的指令為iadd,含義是將棧頂兩int類型的元素相加,并將相加的結果壓入棧頂,得到的是1;

  • 讀入AC,查表得0xAC對應的指令為ireturn,含義是將當前方法的結果1,返回;

?exception_table_length為000001605-000001606位置的0x0000,表示exception_table_length為0,后面沒有exception相關數據;緊接著兩個字節表示attributes_count,即位置000001607-0000016080x0002,表示有2個attribute;分析Code屬性結構中的兩個屬性如下:

  • 第1個屬性:
    ?attribute_name_index為000001609-00000160A位置的0x000A,表示引用常量池的第10個常量,也即LineNumberTable,LineNumberTable的屬性結構如下:
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];
}

?attribute_length為00000160B-00000160E位置的0x00000006,表示屬性長度為6;line_number_table_length為00000160F-000001700位置的0x0001,也即擁有一個line_number_table;后面緊跟著line_number_table,其中start_pc為000001701-000001702位置的0x0000,line_number為000001703-000001704位置0x0010;

  • 第2個屬性:
    ?attribute_name_index為000001705-000001706位置的0x000B,表示引用常量池的第11個常量,也即LocalVariableTable,LocalVariableTable的屬性結構如下:
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];
}

?attribute_length為000001707-00000170A位置的0x0000000C,表示屬性長度為12;local_variable_table_length為00000170B-00000170C位置的0x0001,表示local_variable_table的長度為1;緊接著local_variable_table的分析,其中start_pc為00000170D-00000170E位置的0x0000,length為00000170F-000001800位置的0x0007,name_index為000001801-000001802位置的0x000C,表示name為常量池的第12個屬性,也即this,descriptor_index為000001803-000001804位置的0x000D,也即常量池的第13個屬性,也即Lcom/sunny/jdk/classfile/TestClass;,index為000001805-000001806位置的0x0000;最后我們來分析下class的文件屬性;

8. class文件屬性:u2 attributes_count 和 attributes[attributes_count]

?attributes_count 為000001807-000001808位置的0x0001,表示有一個屬性;attribute_name_index為000001809-00000180A位置的0x0010,表示常量池中的第16個屬性,也SourceFile屬性;SourceFile屬性結構如下:

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

?attribute_length為位置的00000180B-00000180E位置的0x00000002,表示屬性長度為2;sourcefile_index為00000180F-000001900位置的0x0011,表示常量池的第17個常量,也即TestClass.java
?至此,整個class的二進制碼全部分析完畢;但是JDK也給我們提供了更方便的分析工具,也即文中提到的javap -verbose class文件命令。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376