总体来说,java的class结构定义按照定义顺序如下:
1.魔数(u4) : 固定开头"CA FE BA BE",4个字节
2.版本号(u4) : 4个字节,前两个字节为小版本号,后两个字节为大版本号,以jdk1.7为例,标准版本号为"00 00 00 33",若为jdk1.8,则版本号为"00 00 00 34"
3.常量池 : 定义类中引用到的所有常量。头两个字节表示常量个数,实际值为常量个数减1。后面的结构以引用称呼的,就是引用了常量池的内容。
常量池存放的内容包括如下几类数据:
- 类和接口的全限定名(即带有包名的 Class 名,如:org.lxh.test.TestClass)
- 字段的名称和描述符(private、static 等描述符)
- 方法的名称和描述符(private、static 等描述符)
对于所有常量类型,都有自己独有的结构,但是每个常量类型头一个字节都是用作常量的类型标记,即下表所示tag字段,只有读取了头一个字节才能知道这是什么类型的常量,进而继续解析。
下表给出了常量池中 14 种数据类型的结构:
常量 | 项目 | 类型 | 描述 |
CONSTANT_Utf8_info | tag | u1 | 值为1 |
length | u2 | UF-8编码的字符串占用的字节数 | |
bytes | u1 | 长度为length的UTF-8编码的字符串 | |
CONSTANT_Integer_info | tag | u1 | 值为3 |
bytes | u4 | 按照高位在前存储的int值 | |
CONSTANT_Float_info | tag | u1 | 值为4 |
bytes | u4 | 按照高位在前存储的float值 | |
CONSTANT_Long_info | tag | u1 | 值为5 |
bytes | u8 | 按照高位在前存储的long值 | |
CONSTANT_Double_info | tag | u1 | 值为6 |
bytes | u8 | 按照高位在前存储的double值 | |
CONSTANT_Class_info | tag | u1 | 值为7 |
index | u2 | 指向全限定名常量项的索引 | |
CONSTANT_String_info | tag | u1 | 值为8 |
index | u2 | 指向字符串字面量的索引 | |
CONSTANT_Fieldref_info | tag | u1 | 值为9 |
index | u2 | 指向声明字段的类或接口描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向字段名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_Methodref_info | tag | u1 | 值为10 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向方法名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_InrerfaceMethodref_info | tag | u1 | 值为11 |
index | u2 | 指向声明方法的接口描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向方法名称及类型描述符CONSTANT_NameAndType_info的索引项 | |
CONSTANT_NameAndType_info
| tag | u1 | 值为12 |
index | u2 | 指向字段或方法名称常量项目的索引 | |
index | u2 | 指向该字段或方法描述符常量项的索引 | |
CONSTANT_Method-Handle_info | tag | u1 | 值为15 |
reference_kind | u1 | [1,9],决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为 | |
reference_index | u2 | 值必须为常量池的有效索引 | |
CONSTANT_Method-Type_info | tag | u1 | 值为16 |
descriptor_index | u2 | 值必须为常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符。 | |
CONSTANT_Invoke-Dynamic_info | tag | u1 | 值为18 |
bootstrap_method_attr_index | u2 | 值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
4.访问标识(u2):两个字节,标识类的访问限制属性。
5.类索引(u2)、父类索引(u2)、接口索引集合(u2+n*u2):2个字节表示类索引,引用CONSTANT_Class_info类型,2个字节表示父类索引,引用CONSTANT_Class_info字节类型。由于接口可以实现多个,头两个字节表示实现了多少个接口,假设为n个,那么后2n个字节分别为n个接口在常量池中的引用。
6.字段表集合:访问限制标识(u2)+名称引用(u2)+类型引用(u2)+属性表集合(由属性个数标识u2+属性描述列表组成)
7.方法表集合:访问限制标识(u2)+名称引用(u2)+类型引用(u2)+属性表集合(由属性个数标识u2+属性描述列表组成),这里值得一提的是,每个方法的属性表中都有一个名称为“Code”的属性(这里说非抽象方法,抽象方法未确认),它的内容就是方法的实现,所以这一块可能有相当长的内容。
8.属性表集合:由属性个数标识u2+属性描述列表组成,一般为类的属性或者类文件属性。
总体用表格来描述就如下所示:
下面就一个class文件示例做下二进制解析:
1.首先源码如下,一个很简单的类:
public class ForByteCode {
int aInt=0;
final int bFinInt=1;
String strTest="xxteststr";
final String finStr="finalstr";
int tesMet(){
return aInt++;
}
private void testMeth2(){
System.out.println("sysout.pprint");
}
}
2.生成class文件后,二进制如下,还是比较大的。这里使用的1.7的jdk。
CA FE BA BE 00 00 00 33 00 35 07 00 02 01 00 0B
46 6F 72 42 79 74 65 43 6F 64 65 07 00 04 01 00
10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63
74 01 00 04 61 49 6E 74 01 00 01 49 01 00 07 62
46 69 6E 49 6E 74 01 00 0D 43 6F 6E 73 74 61 6E
74 56 61 6C 75 65 03 00 00 00 01 01 00 07 73 74
72 54 65 73 74 01 00 12 4C 6A 61 76 61 2F 6C 61
6E 67 2F 53 74 72 69 6E 67 3B 01 00 06 66 69 6E
53 74 72 08 00 0E 01 00 08 66 69 6E 61 6C 73 74
72 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56
01 00 04 43 6F 64 65 0A 00 03 00 13 0C 00 0F 00
10 09 00 01 00 15 0C 00 05 00 06 09 00 01 00 17
0C 00 07 00 06 08 00 19 01 00 09 78 78 74 65 73
74 73 74 72 09 00 01 00 1B 0C 00 0A 00 0B 09 00
01 00 1D 0C 00 0C 00 0B 01 00 0F 4C 69 6E 65 4E
75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63
61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01
00 04 74 68 69 73 01 00 0D 4C 46 6F 72 42 79 74
65 43 6F 64 65 3B 01 00 06 74 65 73 4D 65 74 01
00 03 28 29 49 01 00 09 74 65 73 74 4D 65 74 68
32 09 00 26 00 28 07 00 27 01 00 10 6A 61 76 61
2F 6C 61 6E 67 2F 53 79 73 74 65 6D 0C 00 29 00
2A 01 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F
69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 08
00 2C 01 00 0D 73 79 73 6F 75 74 2E 70 70 72 69
6E 74 0A 00 2E 00 30 07 00 2F 01 00 13 6A 61 76
61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
0C 00 31 00 32 01 00 07 70 72 69 6E 74 6C 6E 01
00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74
72 69 6E 67 3B 29 56 01 00 0A 53 6F 75 72 63 65
46 69 6C 65 01 00 10 46 6F 72 42 79 74 65 43 6F
64 65 2E 6A 61 76 61 00 21 00 01 00 03 00 00 00
04 00 00 00 05 00 06 00 00 00 10 00 07 00 06 00
01 00 08 00 00 00 02 00 09 00 00 00 0A 00 0B 00
00 00 10 00 0C 00 0B 00 01 00 08 00 00 00 02 00
0D 00 03 00 01 00 0F 00 10 00 01 00 11 00 00 00
59 00 02 00 01 00 00 00 1B 2A B7 00 12 2A 03 B5
00 14 2A 04 B5 00 16 2A 12 18 B5 00 1A 2A 12 0D
B5 00 1C B1 00 00 00 02 00 1E 00 00 00 1A 00 06
00 00 00 02 00 04 00 04 00 09 00 05 00 0E 00 06
00 14 00 07 00 1A 00 02 00 1F 00 00 00 0C 00 01
00 00 00 1B 00 20 00 21 00 00 00 00 00 22 00 23
00 01 00 11 00 00 00 36 00 04 00 01 00 00 00 0C
2A 59 B4 00 14 5A 04 60 B5 00 14 AC 00 00 00 02
00 1E 00 00 00 06 00 01 00 00 00 09 00 1F 00 00
00 0C 00 01 00 00 00 0C 00 20 00 21 00 00 00 02
00 24 00 10 00 01 00 11 00 00 00 37 00 02 00 01
00 00 00 09 B2 00 25 12 2B B6 00 2D B1 00 00 00
02 00 1E 00 00 00 0A 00 02 00 00 00 0C 00 08 00
0D 00 1F 00 00 00 0C 00 01 00 00 00 09 00 20 00
21 00 00 00 01 00 33 00 00 00 02 00 34
3.解析。这里就16进制的内容,将二进制所有组成部分拆开看,其中方法表的Code属性的内容实际上就是方法的实现,这里暂时不对其含义进行解析。结果如下:
CA FE BA BE 魔数
00 00 00 33 jdk1.7
-------------------------以下是常量池
00 35 35(34)/53(52)个常量
07 00 02 #1为 Class_info ,引用#2
01 00 0B #2为utf8_info,长度0B(11),值为”ForByteCode“
46 6F 72 42 79 74 65 43 6F 64 65 ”ForByteCode“
07 00 04 #3为1为Class_info,引用#4
01 00 10 #4为utf8_info,长度10(16),值为”java/lang/Object“
6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
01 00 04 #5为 utf8_info ,长度4,值”aInt“
61 49 6E 74
01 00 01 #6为 utf8_info ,长度1,值”I“
49
01 00 07 #7为 utf8_info ,长度7,值”bFinInt“
62 46 69 6E 49 6E 74
01 00 0D #8 为 utf8_info, 长度13 , 值”ConstantValue“
43 6F 6E 73 74 61 6E 74 56 61 6C 75 65
03 #9为 Integer_info ,值为1
00 00 00 01
01 00 07 #10为 utf8_info ,值为”strTest“
73 74 72 54 65 73 74
01 00 12 #11为 utf8_info ,值为”Ljava/lang/String;“
4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B
01 00 06 #12为 utf8_info ,值为”finStr“
66 69 6E 53 74 72
08 00 0E #13为 String_info ,引用#0E,即#14
01 00 08 #14为 utf8_info ,值”finalstr“
66 69 6E 61 6C 73 74 72
01 00 06 #15为 utf8_info ,值”<init>“
3C 69 6E 69 74 3E
01 00 03 #16为 utf8_info ,值”()V“
28 29 56
01 00 04 #17为 utf8_info ,值”Code“
43 6F 64 65
0A 00 03 00 13 #18为 Methodref_info ,申明的类为#3 ,属性引用#19
0C 00 0F 00 10 #19为 NameAndType_info ,名称引用#15 类型引用#16
09 00 01 00 15 #20为 Fieldref_info ,申明类为#1 ,属性引用#21
0C 00 05 00 06 #21为 NameAndType_info ,名称引用#5,类型引用#6
09 00 01 00 17 #22为 Fieldref_info ,申明类为#1 , 属性引用 #23
0C 00 07 00 06 #23为 NameAndType_info ,名称引用#7, 类型引用#6
08 00 19 #24为 String_info , 引用#25
01 00 09 #25为 utf8_info , 值为”xxteststr“
78 78 74 65 73 74 73 74 72
09 00 01 00 1B #26为 Fieldref_info , 申明类为#1,属性引用#27
0C 00 0A 00 0B #27为 NameAndType_info ,名称引用#10,, 类型引用#11
09 00 01 00 1D #28为 Fieldref_info , 申明类为#1,属性引用#29
0C 00 0C 00 0B #29为 NameAndType_info , 名称引用#12 , 类型引用#11
01 00 0F #30为 utf8_info , 值为 “LineNumberTable”
4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
01 00 12 #31为 utf8_info , 值为 “LocalVariableTable”
4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65
01 00 04 #32为 utf8_info , 值为 “this”
74 68 69 73
01 00 0D #33为 utf8_info , 值为 “LForByteCode;”
4C 46 6F 72 42 79 74 65 43 6F 64 65 3B
01 00 06 #34为 utf8_info , 值为 “tesMet”
74 65 73 4D 65 74
01 00 03 #35为 utf8_info , 值为 “()I”
28 29 49
01 00 09 #36为 utf8_info , 值为 “testMeth2”
74 65 73 74 4D 65 74 68 32
09 00 26 00 28 #37为 Fieldref_info , 申明类为#38,属性引用#40
07 00 27 #38为 Class_info ,引用#39
01 00 10 #39为 utf8_info , 值为 “java/lang/System”
6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
0C 00 29 00 2A #40为 NameAndType_info ,名称引用#41 , 类型引用#42
01 00 03 #41为 utf8_info , 值为 out
6F 75 74
01 00 15 #42为 utf8_info , 值为 Ljava/io/PrintStream;
4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B
08 00 2C #43为String_info , 引用#44
01 00 0D #44为 utf8_info , 值为 “sysout.pprint”
73 79 73 6F 75 74 2E 70 70 72 69 6E 74
0A 00 2E 00 30 #45为 Methodref_info ,申明的类为#46 ,属性引用#48
07 00 2F #46为 Class_info , 引用#47
01 00 13 #47为 utf8_info , 值为 ”java/io/PrintStream“
6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
0C 00 31 00 32 #48为 NameAndType_info , 名称引用#49 , 类型引用#50
01 00 07 #49为 utf8_info , 值为 ”println“
70 72 69 6E 74 6C 6E
01 00 15 #50为 utf8_info , 值为 ”(Ljava/lang/String;)V“
28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
01 00 0A #51为 utf8_info , 值为 ”SourceFile“
53 6F 75 72 63 65 46 69 6C 65
01 00 10 #52为 utf8_info , 值为”ForByteCode.java“
46 6F 72 42 79 74 65 43 6F 64 65 2E 6A 61 76 61
----------------------访问标记
00 21 即 0000 0000 0010 0001,即 ACC_SUPER|ACC_PUBLIC ,其中ACC_SUPER在jdk1.0.2之后都为真,ACC_PUBLIC表示为public类
-----------------------类索引、父类索引、接口索引集合
00 01 类索引,指向#1
00 03 父类索引,指向#3
00 00 接口索引集合,第一个u2为索引计数器,值为0,表示没有继承接口
------------------------字段表集合
00 04 fields_count计数器,4个字段
访问标识u2 名称u2 描述符u2 属性表(len:u2+content)
00 00 00 05 00 06 00 00 NULL #5 "aInt" #6 len=0
00 10 00 07 00 06 00 01 ACC_FINAL #7 "bFinInt" #6 len=1,
00 08 00 00 00 02 00 09 属性1 , 属性名称引用#8 , 值引用#9 ,即1
00 00 00 0A 00 0B 00 00 NULL #10 "strTest" #11 len=0
00 10 00 0C 00 0B 00 01 ACC_FINAL #12 "finStr" #11 len=1,
00 08 00 00 00 02 00 0D 属性1 , 属性名称引用#8 , 值引用#13 -> #14 ,即"finalstr"
------------------------方法表
00 03 3个方法
访问标识u2 名称u2 描述符u2 属性表
00 01 00 0F 00 10 00 01 ACC_PUBLIC;名称#15 "<init>";描述符指示方法类型#16 "()V";属性个数为1
00 11 00 00 00 59 属性名称"Code",长度0x00000059,以下为属性值,也即方法实现
00 02 00 01 00 00 00 1B 2A B7 00 12 2A 03 B5
00 14 2A 04 B5 00 16 2A 12 18 B5 00 1A 2A 12 0D
B5 00 1C B1 00 00 00 02 00 1E 00 00 00 1A 00 06
00 00 00 02 00 04 00 04 00 09 00 05 00 0E 00 06
00 14 00 07 00 1A 00 02 00 1F 00 00 00 0C 00 01
00 00 00 1B 00 20 00 21 00 00
00 00 00 22 00 23 00 01 默认访问标识;名称#34 "tesMet";描述符指示方法类型#35 "()I";属性个数为1
00 11 00 00 00 36 属性名称"Code",长度0x00000036,以下为属性值,也即方法实现
00 04 00 01 00 00 00 0C
2A 59 B4 00 14 5A 04 60 B5 00 14 AC 00 00 00 02
00 1E 00 00 00 06 00 01 00 00 00 09 00 1F 00 00
00 0C 00 01 00 00 00 0C 00 20 00 21 00 00
00 02 00 24 00 10 00 01 ACC_PRIVATE;名称#36 "testMeth2";描述符指示方法类型#16 "()V";
00 11 00 00 00 37 属性名称"Code" , 长度0x00000037,以下为属性值,也即方法实现
00 02 00 01
00 00 00 09 B2 00 25 12 2B B6 00 2D B1 00 00 00
02 00 1E 00 00 00 0A 00 02 00 00 00 0C 00 08 00
0D 00 1F 00 00 00 0C 00 01 00 00 00 09 00 20 00
21 00 00
-------------属性表集合
00 01
name(u2) len(u4) info(len)
00 33 00 00 00 02 00 34 名称#51 "SourceFile";长度0x00000002;引用#52 "ForByteCode.java"