?在《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
,有上面的關于常量類型的描述可知tag
為10
的常量是Constant_Methodref_info
,而Constant_Methodref_info
的結構如下:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
?其中tag
對應上圖位置00000000A
處的內容為0x0A
;class_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
處的內容為0x07
;name_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
-000001209
的0x0002
,表示有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是00000140
C-00000140
D位置的
0x000F,也即常量池的第15個常量,也即
()I,表示返回值為int;attributes_count是00000140
E-00000140
F位置的
0x0001,表示有inc方法有
1個屬性,后面緊跟的就是屬性分析; *inc方法的第1個屬性: attribute_name_index是00000150
0-00000150
1位置的
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
-000001608
的0x0002
,表示有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文件
命令。