在《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文件
命令。