class文件2-字节码阅读
本文在class文件1-文件格式的基础上,以阅读class文件的形式介绍常量池(Constant Pool) Method Attribute结构。
class文件的具体内容除了Constant_Pool Method Attribute,还有Interface Field,但是由于本文的示例代码没有接口和Field,故本文没有列出
Java文件源码,jdk版本是13.0.2,JVM是HotSpot,但是是通过IDEA进行build的
public class HelloWorld {
public static void main(String[] args) {
String hello = "Hello World";
System.out.println(hello);
}
}
对应的class文件如下:
cafe babe 0000 0039 0024 0a00 0200 0307
0004 0c00 0500 0601 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0100 063c 696e
6974 3e01 0003 2829 5608 0008 0100 0b48
656c 6c6f 2057 6f72 6c64 0900 0a00 0b07
000c 0c00 0d00 0e01 0010 6a61 7661 2f6c
616e 672f 5379 7374 656d 0100 036f 7574
0100 154c 6a61 7661 2f69 6f2f 5072 696e
7453 7472 6561 6d3b 0a00 1000 1107 0012
0c00 1300 1401 0013 6a61 7661 2f69 6f2f
5072 696e 7453 7472 6561 6d01 0007 7072
696e 746c 6e01 0015 284c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b29 5607 0016
0100 1b70 7269 2f6c 6a77 2f6a 6176 612f
6a76 6d2f 4865 6c6c 6f57 6f72 6c64 0100
0443 6f64 6501 000f 4c69 6e65 4e75 6d62
6572 5461 626c 6501 0012 4c6f 6361 6c56
6172 6961 626c 6554 6162 6c65 0100 0474
6869 7301 001d 4c70 7269 2f6c 6a77 2f6a
6176 612f 6a76 6d2f 4865 6c6c 6f57 6f72
6c64 3b01 0004 6d61 696e 0100 1628 5b4c
6a61 7661 2f6c 616e 672f 5374 7269 6e67
3b29 5601 0004 6172 6773 0100 135b 4c6a
6176 612f 6c61 6e67 2f53 7472 696e 673b
0100 0568 656c 6c6f 0100 124c 6a61 7661
2f6c 616e 672f 5374 7269 6e67 3b01 000a
536f 7572 6365 4669 6c65 0100 0f48 656c
6c6f 576f 726c 642e 6a61 7661 0021 0015
0002 0000 0000 0002 0001 0005 0006 0001
0017 0000 002f 0001 0001 0000 0005 2ab7
0001 b100 0000 0200 1800 0000 0600 0100
0000 0900 1900 0000 0c00 0100 0000 0500
1a00 1b00 0000 0900 1c00 1d00 0100 1700
0000 4700 0200 0200 0000 0b12 074c b200
092b b600 0fb1 0000 0002 0018 0000 000e
0003 0000 000c 0003 000d 000a 000e 0019
0000 0016 0002 0000 000b 001e 001f 0000
0003 0008 0020 0021 0001 0001 0022 0000
0002 0023
此处是通过sublime打开class文件,为了展示美观自动添加了空格作为分隔符,实际上是不存在这些空格的,后文中也会重新按照对应的结构重新分割
反编译class文件展示如下
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package pri.ljw.java.jvm;
public class HelloWorld {
public HelloWorld() {
}
public static void main(String[] args) {
String hello = "Hello World";
System.out.println(hello);
}
}
Constant Pool
为了便于理解,首先通过IDEA的JClasslib概览一下Constant Pool内容
- 常量池大小为35,对应的Constant_pool_count为36(0x0024)
字节码 | 含义 |
---|---|
0024 | 常量池大小 Constant_pool_count |
在《The Java® Virtual Machine Specification Java SE 13 Edition》文档的4.4节分12小节介绍了18种结构,本文只展示用到的6种结构,其余12种结合本文和官方文档查看也十分简单,此处不做赘述。
CONSTANT_Methodref_info
CONSTANT_Methodref_info,指向声明方法的类或接口的描述符
1.
cp_info #2 <java/lang/Object>
#2对应常量池的2号位(即 [02]CONSTANT_Class_info)
2. cp_info #3 <<init> : () V>
#3对应常量池的3号位(即 [03]Constant_NameAndType_info)
// 总字节为5
CONSTANT_Methodref_info {
u1 tag; // 固定为10,即0x0a
u2 class_index;
u2 name_and_type_index;
}
[01]对应的字节码为:
0a 0002 0003
字节码 | 含义 |
---|---|
0a | tag -10,表示CONSTANT_Methodref_info |
0002 | class_index - 2,对应常量池的2号位 |
0003 | name_and_type_index - 3,对应常量池的3号位 |
CONSTANT_Class_info
CONSTANT_Class_info,表示一个class或者interface
#4对应到4号位([04]CONSTANT_Utf8_info),表示class名称信息
// 总字节为3
CONSTANT_Class_info {
u1 tag; // 固定为7,即0x07
u2 name_index;
}
[02]对应的字节码为
07 0004
字节码 | 含义 |
---|---|
07 | tag - 7,表示CONSTANT_Class_info |
0004 | name_index - 4,对应常量池的4号位 |
CONSTANT_NameAndType_info
CONSTANT_NameAndType_info,表示一个field或者method
同上,相关结构如下:
// 总字节为5
CONSTANT_NameAndType_info {
u1 tag; // 固定为12,即0x0c
u2 name_index;
u2 descriptor_index;
}
[03]对应的字节码为
0c 0005 0006
字节码 | 含义 |
---|---|
0c | tag - 12,表示CONSTANT_NameAndType_info |
0005 | name_index - 5,对应常量池的5号位 |
0006 | descriptor_index - 6,对应常量池的6号位 |
CONSTANT_Utf8_info
CONSTANT_Utf8_info,表示字符常量值
// 总字节为4
CONSTANT_Utf8_info {
u1 tag; // 固定为1,即0x01
u2 length;
u1 bytes[length];
}
下面分别对[04] [05] [06]三个常量位进行字节码翻译
- [04]CONSTANT_Utf8_info对应的字节码
01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374
字节码 | 含义 |
---|---|
01 | tag - 1,表示CONSTANT_Utf8_info |
0010 | length - 16,当前表示className的长度(java/lang/Object) |
6a…74 | bytes[length],对应具体的字符串java/lang/Object |
将java/lang/Object
(UTF8编码)以16进制表示,即为
6a61 7661 2f6c 616e 672f 4f62 6a65 6374
- [05]CONSTANT_Utf8_info对应的字节码
01 0006 3c69 6e69 743e
字节码 | 含义 |
---|---|
01 | tag - 1,表示CONSTANT_Utf8_info |
0006 | length - 6,当前表示方法名的长度 |
3c…3e | bytes[length],对应具体的字符串<init> |
将<init>
(UTF8编码)以16进制表示,即为
3c69 6e69 743e
- [06]CONSTANT_Utf8_info字节码翻译如下
01 0003 2829 56
字节码 | 含义 |
---|---|
01 | tag - 1,表示CONSTANT_Utf8_info |
0003 | length - 3,当前表示方法返回参数的长度 |
282956 | bytes[length],对应具体的字符串()V |
将()V
(UTF8编码)以16进制表示,即为
2829 56
CONSTANT_String_info
CONSTANT_String_info,指向字符串对象
// 总字节为3
CONSTANT_String_info {
u1 tag; // 固定为8,即0x08
u2 string_index;
}
[07]字节码为
08 0008
[08]字节码为
0100 0b 4865 6c6c 6f20 576f 726c 64
字节码 | 含义 |
---|---|
08 | tag - 8,表示CONSTANT_String_info |
0008 | string_index - 8,指向常量池8号位([08]CONSTANT_Utf8_info) |
01 | tag - 1,表示CONSTANT_Utf8_info |
000b | length - 11,当前表示字符串长度(Hello World) |
48…64 | bytes[length],对应具体的字符串Hello World |
将Hello World
以16进制表示,即为
4865 6c6c 6f20 576f 726c 64
[07][08]对应的是Hello World字符串
CONSTANT_Fieldref_info
CONSTANT_Fieldref_info,指向声明Field的类或接口的描述符
// 总字节为5
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
[09]对应的字节码为
09 000a 000b
字节码 | 含义 |
---|---|
09 | tag - 9,表示CONSTANT_Fieldref_info |
000a | class_index - 10,指向常量池10号位([10]CONSTANT_class_info) |
000b | name_and_type_index - 11,表示指向常量池10号位([10]CONSTANT_NameAndType_info) |
至此,HelloWorld涉及到的常量池内容已翻译完毕,下文直接给出所有的常量位的字节码翻译
class文件的常量池字节码阅读
常量位 | 字节码 | 含义 | 备注 |
---|---|---|---|
01 | 0a 0002 0003 | Methodref,#2.# 3 | java/lang/Object.<init>:()V |
02 | 07 0004 | Class,#4 | java/lang/Object |
03 | 0c 0005 0006 | NameAndType,#5:#6 | <init>:()V |
04 | 01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 | Utf8 | java/lang/Object |
05 | 01 0006 3c 696e 6974 3e | Utf8 | <init> |
06 | 01 0003 2829 56 | Utf8 | ()V |
07 | 08 0008 | String | Hello World |
08 | 01 000b 48 656c 6c6f 2057 6f72 6c64 | Utf8 | Hello World |
09 | 09 000a 000b | Fieldref #10.#11 | java/lang/System.out:Ljava/io/PrintStream; |
10 | 07 000c | Class #12 | java/lang/System |
11 | 0c 000d 000e | NameAndType #13:#14 | out:Ljava/io/PrintStream; |
12 | 01 0010 6a61 7661 2f6c 616e 672f 5379 7374 656d | Utf8 | java/lang/System |
13 | 01 0003 6f 7574 | Utf8 | out |
14 | 01 0015 4c 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d3b | Utf8 | Ljava/io/PrintStream; |
15 | 0a 0010 0011 | Methodref,#16.#17 | java/io/PrintStream.println:(Ljava/lang/String;)V |
16 | 07 0012 | Class,#18 | java/io/PrintStream |
17 | 0c 0013 0014 | NameAndType,#19:#20 | println:(Ljava/lang/String;)V |
18 | 01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d | Utf8 | java/io/PrintStream |
19 | 01 0007 7072 696e 746c 6e | Utf8 | println |
20 | 01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 56 | Utf8 | (Ljava/lang/String;)V |
21 | 07 0016 | Class,#22 | pri/ljw/java/jvm/HelloWorld |
22 | 01 001b 70 7269 2f6c 6a77 2f6a 6176 612f 6a76 6d2f 4865 6c6c 6f57 6f72 6c64 | Utf8 | pri/ljw/java/jvm/HelloWorld |
23 | 01 0004 43 6f64 65 | Utf8 | Code |
24 | 01 000f 4c69 6e65 4e75 6d62 6572 5461 626c 65 | Utf8 | LineNumberTable |
25 | 01 0012 4c6f 6361 6c56 6172 6961 626c 6554 6162 6c65 | Utf8 | LocalVariableTable |
26 | 01 0004 74 6869 73 | Utf8 | this |
27 | 01 001d 4c70 7269 2f6c 6a77 2f6a 6176 612f 6a76 6d2f 4865 6c6c 6f57 6f72 6c64 3b | Utf8 | Lpri/ljw/java/jvm/HelloWorld; |
28 | 01 0004 6d61 696e | Utf8 | main |
29 | 01 0016 28 5b4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 56 | Utf8 | ([Ljava/lang/String;)V |
30 | 01 0004 6172 6773 | Utf8 | args |
31 | 01 0013 5b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b | Utf8 | [Ljava/lang/String; |
32 | 01 0005 68 656c 6c6f | Utf8 | hello |
33 | 01 0012 4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b | Utf8 | Ljava/lang/String; |
34 | 01 000a 536f 7572 6365 4669 6c65 | Utf8 | SourceFile |
35 | 01 000f 48 656c 6c6f 576f 726c 642e 6a61 7661 | Utf8 | HelloWorld.java |
以上就是常量池的字节码阅读,其中Code
,LineNumberTable
,LocalVariableTable
是JVM预定义好的属性字段
-
Code
- Code属性是method_info结构的属性表中的可变长度属性
- Code属性包含Java虚拟机指令和方法的辅助信息,包括实例初始化方法和类或接口初始化方法
- 后文会在Method中看到源码阅读解析
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]; }
Code属性中的结构字段具体含义,可以参考《The Java® Virtual Machine Specification Java SE 13 Edition》文档的的4.7.3节。
-
LineNumberTable
- LineNumberTable属性是Code属性的属性表中的一个可选的可变长度属性。 Debugger可以使用它来确定代码数组的哪一部分与原始源文件中的给定行号相对应。(可参考文档4.7.12)
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]; }
-
LocalVariableTable
- LocalVariableTable属性是Code属性的属性表中的一个可选的可变长度属性。 Debugger可以使用它来确定方法执行期间给定局部变量的值。(可参考文档4.7.13)
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]; }
Methods
Method表示当前类的方法信息,基本结构如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
对应的字节码文件如下:
// 常量池内容
... ... ... 6c6f 576f 726c 642e 6a61 7661
// access_flag this super interface field等内容
0021 0015 0002 0000 0000
// method_count
0002
// 构造方法
0001 0005 0006
// 构造方法的Code属性[基本信息 指令信息 LineNumberTable LocalVariableTable]
0001 0017 0000 002f 0001 0001 0000 0005 2a b7 0001 b1 0000 0002
0018 0000 0006 0001 0000 0009
0019 0000 000c 0001 0000 0005 001a 001b 0000
按照构造方法和main方法分开进行字节码翻译
有关Java源文件行号,需要对应反编译后的class文件进行验证
JVM指令集相关内容,如指令对应的16进制及其含义,可参考JVM官方文档中的第6.4节
有关method的access flag信息可参考官方文档的Table 4.6 A
常量池${num}号位,简写为 #num,如:常量池24号位,简写为 #24
部分字段含义参考 class文件属性表解析
method_count 和 构造方法
序号 | 字节码 | 含义 | 备注 |
---|---|---|---|
1 | 0002 | method_count | 方法数量,2个,空构造方法和main方法 |
2 | 0001 | access_flag | 构造方法的访问修饰符,public |
3 | 0005 | name_index | #5,<init>,表示构造方法 |
4 | 0006 | descriptor_index | #6,()V,构造方法的描述 |
5 | 0001 | attributes_count | 属性数量,1,Code |
6 | 0017 | attribute_name_index | #23,Code |
7 | 0000 002f | attribute_length | 属性值长度47 |
8 | 0001 | max_stack | 操作数栈的最大深度,此处为1 |
9 | 0001 | max_locals | 局部变量表所需要的存储空间,单位为slot |
10 | 0000 0005 | code_length | 字节码指令长度,此处为5 |
11 | 2a | 指令 | 0x2a,对应 aload_0,表示加载栈中的index为0的引用,指令长度累计为1 |
12 | b7 | 指令 | 0xb7,对应 invokespecial,指令长度累计为2 |
13 | 0001 | 指令执行参数 | #1,对应空构造方法,指令长度累计为4 |
14 | b1 | 指令 | 0xb1,对应 return |
15 | 0000 | exception_table_length | 异常表占用的字节数,此处为0 |
16 | 0002 | attributes_count | Code属性的子属性个数,此处为2 |
17 | 0018 | attribute_name_index | #24,LineNumberTable |
18 | 0000 0006 | attribute_length | 属性值长度6 |
19 | 0001 | line_number_table_length | line_number_table大小,此处为1,属性长度累计为2 |
20 | 0000 | start_pc | 对应Code属性的子属性数组中的下标,此处为0,属性长度累计为4 |
21 | 0009 | line_number | Java源文件行号,此处为9,属性长度累计为6 |
22 | 0019 | attribute_name_index | #25,LocalVariableTable |
23 | 0000 000c | attribute_length | 属性值长度12 |
24 | 0001 | local_variable_table_length | local_variable_table大小,此处为1 |
25 | 0000 | start_pc | 变量生命周期开始时的字节码偏移量,此处为0 |
26 | 0005 | length | 变量作用范围覆盖的字节数,此处为5 |
27 | 001a | name_index | #26,this,表示变量名在常量池中的index |
28 | 001b | descriptor_index | #27,Lpri/ljw/java/jvm/HelloWorld; ,描述该变量名含义的常量池index |
29 | 0000 | index | 在当前栈桢中的位置 |
main方法
main方法的字节码内容紧接上文的构造方法,如下:
// main方法
0009 001c 001d
// 构造方法的Code属性[基本信息 指令信息 LineNumberTable LocalVariableTable]
0001 0017 0000 0047 0002 0002 0000 000b 12 07 4c b2 0009 2b b6 000f b1
0000 0002 0018 0000 000e 0003 0000 000c 0003 000d 000a 000e
0019 0000 0016 0002 0000 000b 001e 001f 0000
0003 0008 0020 0021 0001
序号 | 字节码 | 含义 | 备注 |
---|---|---|---|
1 | 0009 | access_flag | 构造方法的访问修饰符,public static,ACC_PUBLIC(0x0001), ACC_STATIC(0x0008) |
2 | 001c | name_index | #28,main,表示main方法 |
3 | 001d | descriptor_index | #29,([Ljava/lang/String;)V,main方法的描述 |
4 | 0001 | attributes_count | 属性数量,1,Code |
5 | 0017 | attribute_name_index | #23,Code |
6 | 0000 0047 | attribute_length | 属性值长度71 |
7 | 0002 | max_stack | 操作数栈的最大深度,此处为2 |
8 | 0002 | max_locals | 局部变量表所需要的存储空间,单位为slot |
9 | 0000 000b | code_length | 字节码指令长度,此处为11 |
10 | 12 | 指令 | 0x12,对应 ldc,压栈,此处表示将常量池中的Hello World压入栈 |
11 | 07 | 指令执行参数 | #7,String Hello World |
12 | 4c | 指令 | 0x4c,对应 astore_1,存储当前栈桢的index_1的引用 |
13 | b2 | 指令 | 0xb2,对应 getstatic,对应#9, |
14 | 0009 | 指令执行参数 | #9,Field java/lang/System.out:Ljava/io/PrintStream; |
15 | 2b | 指令 | 0x2b,对应 aload_1,加载index_1的引用 |
16 | b6 | 指令 | 0xb6,对应 invokevirtual,执行方法 |
17 | 000f | 指令执行参数 | #15,Method java/io/PrintStream.println:(Ljava/lang/String;)V |
18 | b1 | 指令 | 0xb1,对应 return |
19 | 0000 | exception_table_length | 异常表占用的字节数,此处为0 |
20 | 0002 | attributes_count | Code属性的子属性个数,此处为2 |
21 | 0018 | attribute_name_index | #24,LineNumberTable |
22 | 0000 000e | attribute_length | 属性值长度14 |
23 | 0003 | line_number_table_length | line_number_table大小,此处为3 |
24 | 0000 | start_pc | 字节码偏移量,此处为0 |
25 | 000c | line_number | Java源文件行号,此处为12 |
26 | 0003 | start_pc | 字节码偏移量,此处为3 |
27 | 000d | line_number | Java源文件行号,此处为13 |
28 | 000a | start_pc | 字节码偏移量,此处为10 |
29 | 000e | line_number | Java源文件行号,此处为14 |
30 | 0019 | attribute_name_index | #25,LocalVariableTable |
31 | 0000 0016 | attribute_length | 属性值长度22 |
32 | 0002 | local_variable_table_length | local_variable_table大小,此处为2 |
33 | 0000 | start_pc | 变量生命周期开始时的字节码偏移量,此处为0 |
34 | 000b | length | 变量作用范围覆盖的字节数,此处为 11 |
35 | 001e | name_index | #30,args,表示变量名在常量池中的index |
36 | 001f | descriptor_index | #31,[Ljava/lang/String; ,描述该变量名含义的常量池index |
37 | 0000 | index | 在当前栈桢中的位置 |
38 | 0003 | start_pc | 变量生命周期开始时的字节码偏移量,此处为3 |
39 | 0008 | length | 变量作用范围覆盖的字节数,此处为 8 |
40 | 0020 | name_index | #32,hello,表示变量名在常量池中的index |
41 | 0021 | descriptor_index | #33,Ljava/lang/String; ,描述该变量名含义的常量池index |
42 | 0001 | index | 在当前栈桢中的位置 |
Attributes
Attributes可以理解为一个结构单元,如ClassFile field_info method_info Code_attribute均会使用。
在上文中已经就method_info Code_attribute描述Attributes,本节就classFile进行阐述
字节码文件如下:
0001 0022 0000 0002 0023
对应解释如下:
序号 | 字节码 | 含义 | 备注 |
---|---|---|---|
1 | 0001 | attribute_count | 属性个数,1 |
2 | 0022 | attribute_name_index | #34,对应 SourceFile |
3 | 0000 0002 | attribute_length | 属性长度,2 |
4 | 0023 | name_index | #35,对应source file文件名,HelloWorld.java |
总结
已经详细介绍了constant_pool method attribute相关class字节码阅读,补充一下其他未列出的字节码阅读,
// 常量池内容
6c6f 576f 726c 642e 6a61 7661
// 常量池之后
0021 0015 0002 0000 0000
// 方法和attribute
0002 0001 0005 0006 0001
字节码 | 含义 | 备注 |
---|---|---|
0021 | access_flags | public class, 隐形的含有acc_super |
0015 | this_class | 常量池中的21号位,表示当前类 |
0002 | super_class | 常量池中的2号位,表示父类,对应的是Object类 |
0000 | interface_count | 实现的接口数,此处为0 |
0000 | fields_count | 字段数量 |
至此,以HelloWorld.java为例,对整个class文件的阅读进行详细描述,实际上整个阅读逻辑就是依据class文件结构,大家可以以此为主线,去翻译class字节码文件。
ClassFile {
u4 magic; // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
u2 minor_version; // 为Class文件的副版本,现在基本固定为0x0000
u2 major_version; // 为Class文件的主版本,对应jdk发行版本
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]; // 各种属性
}