4、分析Hello.java的常量池
4.1、constant_pool_count
接下来的两个字节00 12代表常量池里包含的常量数目,也就是说这个常量池包含17个(0x0012 -1 )常量。
4.2、constant_pool 就下来就是分析这17个常量了
1)第一个变量 0a 00 04 00 0e
首先,紧接着constant_pool_count的第一个字节0a(tag=10)表示这是一个CONSTANT_Methodref。CONSTANT_Methodref的结构如下:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
其中class_index表示该方法所属的类在常量池里的索引,name_and_type_index表示该方法的名称和类型的索引。常量池里的变量的索引从1开始。
那么这个methodref结构的数据如下:
0a //tag 表示这是一个CONSTANT_Methodref_info结构
00 04 //指向第4个常量所表示的类
00 0e //指向第14个常量所表示的方法
2)第二个变量
接着是第二个常量,它的tag是09,表示这是一个CONSTANT_Fieldref的结构,它的结构如下:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
和上面的变量基本一致。
09 //tag
00 03//指向第三个常量所表示的类
00 0f //指向第15个常量所表示的变量
3)第三个变量 07 00 10
tag为07表示是一个CONSTANT_Class变量,这个变量的结构如下:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
除了tag字段以外,还有一个name_index是指向该变量名称的一个索引。
4)第四个变量也是一个CONSTANT_Class
5)第五个变量 01 00 04 74 65 73 74
tag为1,表示这是一个CONSTANT_Utf8结构,这种结构用UTF-8的一种变体来表示字符串,结构如下所示:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
其中length表示该字符串的字节数,bytes字段包含该字符串的二进制表示。
接着tag的两个字节0004表示这个字符串的长度是4字节,也即是后面的74657374,表示的是字符串“test” 。
6)接下来的8个变量都是字符串,这里就不具体分析了。
7)第十四个常量 0c 00 07 00 08
tag为0c,表示这是一个CONSTANT_NameAndType结构,这个结构用来描述一个方法或者成员变量。具体结构如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
name_index表示的是该变量或者方法的名称,这里的值是0007,表示指向第7个常量,即是“<init>”。descriptor_index指向该方法的描述符的引用,这里的值是0008,表示指向第8个常量,即是“()V”,由前面描述符的语法可知,这个方法是一个无参的,返回值为void的方法。综合两个字段,可以推出这个方法是“void <init>()”。也即是指向这个NameAndType结构的Methodref的方法名为“void <init>()”,也就是说第一个常量表示的是“void <init>()”方法。
8)第十五个常量也是一个CONSTANT_NameAndType,表示的方法名为“int test()”,第2个常量引用了这个NameAndType,所以第二个常量表示的是“int test()”方法。
9)第16和17个常量也是字符串,可以按照前面的方法分析。
4.3、完整的常量池
最后分析完的常量池如下:
00 12 常量池的数目 18-1=17
0a 00 04 00 0e 方法:java.lang.Ojbect void <init>()
09 00 03 00 0f 方法 :Hello int test()
07 00 10 字符串:Hello
07 00 11 字符串:java.lang.Ojbect
01 00 04 74 65 73 74 字符串:test
01 00 01 49 字符串:I
01 00 06 3c 69 6e 69 74 3e 字符串:<init>
01 00 03 28 29 56 字符串:()V
01 00 04 43 6f 64 65 字符串:Code
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 字符串:LineNumberTable
01 00 03 28 29 49 字符串:()I
01 00 0a 53 6f 75 72 63 65 46 69 6c 65 字符串:SourceFile
01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 字符串:Hello.java
0c 00 07 00 08 NameAndType:<init> ()V
0c 00 05 00 06 NameAndType:test I
01 00 05 48 65 6c 6c 6f 字符串:Hello
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 字符串: java/lang/Object
5、access_flag 00 21
这两个字节的数据表示这个变量的访问标志位,具体的取值见下表:
0x0021=0x0001 | 0x0020 ,也就是表示当前class的access_flag是ACC_PUBLIC|ACC_SUPER。ACC_PUBLIC和代码里的public 关键字相对应。ACC_SUPER表示当用invokespecial指令来调用父类的方法时需要特殊处理。
6、this_class 00 03
this_class存的是当前类的名称在常量池里的索引,这里指向第三个常量,即是“Hello”。
7、super_class 00 04
super_class存的是父类的名称在常量池里的索引,这里指向第四个常量,即是“java/lang/Object”。
8、interfaces
interfaces包含interfaces_count和interfaces[]两个字段。这里interfaces_count为0(0000),所以后面的内容也对应为空。
9、fields
00 01 fields count//表示有一个成员变量
00 02 00 05 00 06 00 00//成员变量的结构
每个成员变量对应一个field_info结构:
field_info {
u2 access_flags; 0002
u2 name_index; 0005
u2 descriptor_index; 0006
u2 attributes_count; 0000
attribute_info attributes[attributes_count];
}
access_flags为0002,即是ACC_PRIVATE
name_index指向常量池的第五个常量,为“test”
descriptor_index指向常量池的第6个常量为“I”
三个字段结合起来,说明这个变量是"private int test"。
接下来的是attribute字段,用来描述该变量的属性,有无这个变量没有附加属性,所以attributes_count为0,attribute_info为空。
10、methods
首先是2个字节的method_count,接下来的内容是两个method_info结构:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
前三个字段和field_info一样,可以分析出第一个方法是“public void <init>()”
00 01 ACC_PUBLIC
00 07 <init>
00 08 V()
接下来是attribute字段,也即是这个方法的附加属性,这里的attributes_count =1,也即是有一个属性。
每个属性的都是一个attribute_info结构,如下所示:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
不同的attribute通过attribute_name_index来区分。JVM规范里对以下attribute进行了预定义:
这里的
attribute_name_index值为0009,表示指向第9个常量,即是Code。Code Attribute的作用是保存该方法的结构如所对应的字节码,具体的结构如下所示:
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所包含的字节数,这里为0000001d,即是39个字节,不包含attribute_name_index和attribute_length字段。
max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,这里是0001
max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,这里是0001.
接下来的code_length表示该方法的所包含的字节码的字节数以及具体的指令码。
这里的字节码长度为00000005,即是后面的5个字节 2a b7 00 01 b1为对应的字节码指令的指令码。
参照下表可以将上面的指令码翻译成对应的助记符:
2a aload_0
b7 invokespecial
00 nop
01 aconst_null
b1 return
这即是该方法被调用时,虚拟机所执行的字节码
接下来是exception_table,这里存放的是处理异常的信息。每个exception_table表项由start_pc,end_pc,handler_pc,catch_type组成。
start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理;handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常,这个可以用来实现finally的功能。
不过,这段代码里没有异常处理exception_table_length为0000,所以我们不做分析。
接下来是该方法的附加属性,attributes_count为0001,表示有一个附加属性。
attribute_name_index为000a,指向第十个常量,为LineNumberTable。这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数。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的名称是LineNumberTable以及长度为00000006。接下来的0001表示line_number_table_length,表示line_number_table有一个表项,其中start_pc为 00 00,line_number为 00 00,表示第0行代码从code的第0个指令码开始。
后面的内容是第二个方法,具体就不再分析了。
11、attributes
最后剩下的内容是attributes,这里的attributes表示整个class文件的附加属性,不过结构还是和前面的attribute保持一致。
00 01表示有一个attribute。
attribute_name_index为000c,指向第12个常量,为SourceFile,说明这个属性是Source Attribute。结构如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_length为00000002
sourcefile_index为000d,表示指向常量池里的第13个常量,为Hello.java。
这个属性表明当前的class文件是从Hello.java文件编译而来。
12、最后分析完的class文件如下所示:
ca fe ba be 魔数,cafe babe
00 00 次版本号 00
00 32 主版本号 50
00 12 常量池的数目 18-1=17
0a 00 04 00 0e java.lang.Ojbect void <init>()
09 00 03 00 0f Hello int
07 00 10 Hello
07 00 11 java.lang.Ojbect
01 00 04 74 65 73 74 test
01 00 01 49 I
01 00 06 3c 69 6e 69 74 3e <init>
01 00 03 28 29 56 ()V
01 00 04 43 6f 64 65 Code
01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 LineNumberTable
01 00 03 28 29 49 ()I
01 00 0a 53 6f 75 72 63 65 46 69 6c 65 SourceFile
01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 Hello.java
0c 00 07 00 08 <init> ()V
0c 00 05 00 06 test I
01 00 05 48 65 6c 6c 6f Hello
01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 java/lang/Object
00 21 access_flag
00 03 this_class
00 04 super_class
00 00 interfaces_count
00 01 fields count
00 02 00 05 00 06 00 00
00 02 method count
00 01 00 07 00 08 00 01 <init>()
00 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00
00 01 00 0a 00 00 00 06 00 01 00 00 00
01
00 01 00 05 00 0b 00 01 int test()
00 09 00 00 00 1d 00
01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
00 0a 00 00 00 06 00 01 00 00 00 03
00 01 00 0c SourceFile
00 00 00 02
00 0d Hello.java