本文将通过对一个简单的Java源文件,编译成的class文件进行分析,以探索Java class文件的格式。
package ex3;
public class ConstantTest {
int a;
public void inc() {
++a;
}
}
将这个Java代码保存为ConstantTest.java
,使用javac ConstantTest.java
,将其编译成ConstantTest.class
字节码文件。使用十六进制查看器hexdump,查看其内容hexdump -C ConstantTest.class
。现在看不懂没关系,下面我们逐字节分析。
00000000 ca fe ba be 00 00 00 34 00 12 0a 00 04 00 0e 09 |.......4........|
00000010 00 03 00 0f 07 00 10 07 00 11 01 00 01 61 01 00 |.............a..|
00000020 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.I...<init>...()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 03 69 6e 63 |umberTable...inc|
00000050 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 11 |...SourceFile...|
00000060 43 6f 6e 73 74 61 6e 74 54 65 73 74 2e 6a 61 76 |ConstantTest.jav|
00000070 61 0c 00 07 00 08 0c 00 05 00 06 01 00 10 65 78 |a.............ex|
00000080 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74 01 00 |3/ConstantTest..|
00000090 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 |.java/lang/Objec|
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
000000b0 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 |................|
000000c0 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
000000d0 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00 03 |................|
000000e0 00 01 00 0b 00 08 00 01 00 09 00 00 00 27 00 03 |.............'..|
000000f0 00 01 00 00 00 0b 2a 59 b4 00 02 04 60 b5 00 02 |......*Y....`...|
00000100 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 |................|
00000110 06 00 0a 00 07 00 01 00 0c 00 00 00 02 00 0d |...............|
0000011f
先看看class文件的反汇编内容javap -v ContantTest.class
。
public class ex3.ConstantTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#15 // ex3/ConstantTest.a:I
#3 = Class #16 // ex3/ConstantTest
#4 = Class #17 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 SourceFile
#13 = Utf8 ConstantTest.java
#14 = NameAndType #7:#8 // "<init>":()V
#15 = NameAndType #5:#6 // a:I
#16 = Utf8 ex3/ConstantTest
#17 = Utf8 java/lang/Object
{
int a;
descriptor: I
flags:
public ex3.ConstantTest();
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 3: 0
public void inc();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field a:I
5: iconst_1
6: iadd
7: putfield #2 // Field a:I
10: return
LineNumberTable:
line 6: 0
line 7: 10
}
class文件结构
Class文件是一组以8bit字节为基础单位的二进制流, 各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。 当遇到需要占用8bit以上空间的数据项时,则会按照高位在前(大端序)的方式分割成若干个字节进行存储。
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
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];
}
Class文件格式可以用上面这个结构体来表示,其中的u2/u4
表示2字节和4字节的数据。我们将其分为以下几个部分来探讨。
- magic
- versioin
- 常量池
- access_flags
- 类索引、父类索引与接口索引集合
- 字段表
- 方法表
- 属性表
magic
magic
是一个四字节的数据,它唯一的功能是确定这个文件是否为一个能被虚拟机接受的Class文件,它的值是固定的0xCAFEBABE
,是Cafe Babe
的意思,这是Java在设计之初就已经决定了的。
使用hexdump -C XXX.class | head -n 2
可以查看class文件的十六进制表示,可以看到它的前4字节是ca fe ba be。
00000000 ca fe ba be 00 00 00 34 00 12 0a 00 04 00 0e 09 |.......4........|
注意:大端序与我们的阅读顺序相同。例如0x12345678中12是高位字节,78是低位字节,而class文件中的第1个字节是低位地址,第4个字节是高位地址,大端序中高位字节放在低位地址,即12放在第1个字节,78放在第4个字节。如果是小端序,就要反过来,在文件中看到的就是78 56 45 12。
Linux中的file命令可以用来判断文件类型,它可以利用这个magic
来判断一个文件是不是class文件。
version
紧接着magic
的4个字节存储的是Class文件的版本号: 第5和第6个字节是minor_version
, 第7和第8个字节是major_version
。
Java的版本号是从45开始的, JDK 1.1之后的每个JDK大版本发布主版本号向上加1( JDK 1.0~1.1使用了45.0~45.3的版本号),高版本的JDK能向下兼容以前版本的Class文件, 但不能运行以后版本的Class文件, 因为《 Java虚拟机规范》 在Class文件校验部分明确要求了即使文件格式并未发生任何变化, 虚拟机也必须拒绝执行超过其版本号的Class文件。
在上面的例子中,minor_version
为00 00,而major_version
为00 34即52,因此该class文件版本号为52.0,是由JDK 8编译而来。
关于次版本号, 曾经在现代Java( 即Java 2)出现前被短暂使用过, JDK 1.0.2支持的版本45.0~45.3( 包括45.0~45.3),JDK 1.1支持版本45.0~45.65535, 从JDK 1.2以后, 直到JDK 12之前次版本号均未使用, 全部固定为零。 而到了JDK 12时期, 由于JDK提供的功能集已经非常庞大, 有一些复杂的新特性需要以“公测”的形式放出, 所以设计者重新启用了副版本号, 将它用于标识“技术预览版”功能特性的支持。 如果Class文件中使用了该版本JDK尚未列入正式特性清单中的预览功能, 则必须把次版本号标识为65535, 以便Java虚拟机在加载类文件时能够区分出来。
常量池
constant_pool_count
是一个u2
类型的数据,代表了常量池容量计数器, 这个容量计数是从1而不是0开始的。如上面例子中的00 12即十进制18,那么常量池的索引范围为1~17。在Class文件格式规范制定之时, 设计者将第0项常量空出来是有特殊考虑的, 这样做的目的在于, 如果后面某些指向常量池的索引值的数据在特定情况下
需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。
常量池中主要存放两大类常量: 字面量( Literal) 和符号引用( Symbolic References) 。
- 字面量比较接近于Java语言层面的常量概念, 如文本字符串、 被声明为final的常量值等。
- 而符号引用则属于编译原理方面的概念, 主要包括下面几类常量:
- 被模块导出或者开放的包( Package)
- 类和接口的全限定名( Fully Qualified Name)
- 字段的名称和描述符( Descriptor)
- 方法的名称和描述符
- 方法句柄和方法类型( Method Handle、 Method Type、 Invoke Dynamic)
- 动态调用点和动态常量( Dynamically-Computed Call Site、 Dynamically-Computed Constant)
以下是JDK 8支持的常量类型。
常量类型 | 标志 | 描述 |
---|---|---|
CONSTANT_Class |
7 | 类或接口的符号引用 |
CONSTANT_Fieldref |
9 | 字段的符号引用 |
CONSTANT_Methodref |
10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref |
11 | 接口中方法的符号引用 |
CONSTANT_String |
8 | 字符串类型字面量 |
CONSTANT_Integer |
3 | 整型字面量 |
CONSTANT_Float |
4 | 浮点型字面量 |
CONSTANT_Long |
5 | 长整形字面量 |
CONSTANT_Double |
6 | 双精度浮点型字面量 |
CONSTANT_NameAndType |
12 | 字段或方法的部分符号引用 |
CONSTANT_Utf8 |
1 | UTF8编码的字符串 |
CONSTANT_MethodHandle |
15 | 方法句柄 |
CONSTANT_MethodType |
16 | 方法类型 |
CONSTANT_InvokeDynamic |
18 | 动态计算常量 |
这些常量类型彼此都没有什么关系,它们是不同的结构体,对应着字节码中一段字节流,这段字节流的长度因常量类型而异。它们的共性是都由1字节的tag
开头。
00000000 ca fe ba be 00 00 00 34 00 12 0a 00 04 00 0e 09 |.......4........|
00000010 00 03 00 0f 07 00 10 07 00 11 01 00 01 61 01 00 |.............a..|
00000020 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.I...<init>...()|
上面例子中常量池的第1项的tag
为0a,是一个Methodref
,它的结构如下,一共占5字节0a 00 04 00 0e。
-
class_index
表示常量池中一个Class
类型的下标,其值为00 04即第4项。 -
name_and_type_index
表示常量池中一个NameAndType
类型的下标,其值为00 0e即第14项。
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
紧接着第2项的tag
为09,是一个Fieldref
,它的结构与Methodref
一样,占用5字节09 00 03 00 0f。
-
class_index
值为00 03即第3项。 -
name_and_type_index
值为00 0f即第15项。
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
第3项的tag
为07,是一个Class
,它的结构如下,一共占用3字节07 00 10。
-
name_index
表示常量池中一个Utf8
类型的下标,其值为00 10即第16项。
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
第4项的tag
为07,同上,指向第17项。
第5项的tag
为01,是一个Utf8
,它的结构如下,长度是变化的。
-
length
是字符串的字节数,其值为00 01即1。 -
bytes
是一个字节数组,其值为0x61即97,是UTF-8编码表示的字符串a
(英文字符的UTF-8编码与ASCII码相同)。看到这里,应该可以猜到,这个是代码中字段a
的名称了。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
第6项的tag
为01
,同上,是一个字符串I
。
第7项的tag
为01
,同上,是一个字符串<init>
。
第8项的tag
为01
,同上,是一个字符串()V
。
第9项的tag
为01
,同上,是一个字符串Code
。
第10项也是字符串LineNumberTable
。
第11项也是字符串inc
。
第12项也是字符串SourceFile
。
第13项也是字符串ConstantTest.java
。
00000070 61 0c 00 07 00 08 0c 00 05 00 06 01 00 10 65 78 |a.............ex|
00000080 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74 01 00 |3/ConstantTest..|
00000090 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 |.java/lang/Objec|
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
第14项tag
为0c
,是一个NameAndType
,其结构如下,一共占用5字节0c 00 07 00 08。
-
name_index
是常量池中一个Utf8_info
的下标,表示方法名称,其值为00 07即7,即<init>
。 -
descriptor_index
也是一个Utf8_info
的下标,表示一个字段描述符(变量类型)或方法描述符(方法参数和返回值类型),其值为00 08即8,即()V
表示方法没有参数,返回void类型。
这一项合起来就是<init>:()V
,描述的是该类中的一个方法,方法名称为<init>
,没有参数,没有返回值。
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
第15项tag
为0c
,同上,合起来是a:I
,描述的是一个字段,字段名称为a
,类型为int
。
第16项tag
为01
,是一个Utf8
,长度为16,内容为65 78 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74,即ex3/ConstantTest
。
第17项tag
为01
,是一个Utf8
,长度为16,内容为6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74,即java/lang/Object
。
access_flags
访问标志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC |
0x0001 | public |
ACC_FINAL |
0x0010 | final |
ACC_SUPER |
0x0020 | JDK 1.0.2之后所有类都必须带上这个标志 |
ACC_INTERFACE |
0x0200 | interface |
ACC_ABSTRACT |
0x0400 | abstract |
ACC_SYNTHETIC |
0x1000 | 标识这个类不是由用户代码产生的 |
ACC_ANNOTATION |
0x2000 | annotation |
ACC_ENUM |
0x4000 | enum |
在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或 者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract 类型;如果是类的话,是否被声明为final;等等。
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
上面例子中的access_flags
值为00 21即,ACC_PUBLIC|ACC_SUPER
,表示这是一个public类。
类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合 (interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。类索 引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。
由于Java语言不允许多 重继承,所以父类索引只有一个,除了java.lang.Object
之外,所有的Java类都有父类,因此除了 java.lang.Object外,所有Java类的父类索引都不为0。
接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements
关键字(如果这个Class文件表示的是一个接口,则应当是extends
关键字)后的接口顺序从左到右排列在接口索引集合中。
他们都是u2类型的数据,是指向常量池中一个Class
类型的索引,而Class
类型的索引则包含指向一个Utf8
的索引。
this_class
是u2类型的数据,是指向常量池中一个Class
类型的索引。上面例子中的值为00 03,即常量池中第3项,指向第16项,内容为ex3/ConstantTest
。
super_class
同上,值为00 04,即第4项,指向第17项,内容为java/lang/Object
。
接口索引集合首先包含一个u2类型的数据,表示接口数量,然后向后寻找接口数量个指向Class
类型的索引。例子中值为00 00,表示没有实现接口。
字段表
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
000000b0 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 |................|
fields_count
是u2数据,表示类拥有多少个字段。在上面例子中值为00 01,表明有1个字段。
field_info
结构用于描述接口或者类中声明的变量。Java语言中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段可以包括的修饰符有字段的作用域(public、private、protected修饰 符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否 强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、 字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫做什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
access_flags
是方法的访问修饰符,与类的access_flags
一样,都是u2
类型。在上面例子中值为00 00,表明没有加修饰符。
标志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC |
0x0001 | public |
ACC_PRIVATE |
0x0002 | private |
ACC_PROTECTED |
0x0004 | protected |
ACC_STATIC |
0x0008 | static |
ACC_FINAL |
0x0010 | final |
ACC_VOLATILE |
0x0040 | volatile |
ACC_TRANSIENT |
0x0080 | transient |
ACC_SYNTHETIC |
0x1000 | 是否由编译器自动产生 |
ACC_ENUM |
0x4000 | enum |
name_index
是名称索引,指向常量池中一个Utf8
,例子中值为00 05即第5项,是字符串a
,表明这个字段名称为a
。
descriptor_index
是描述符索引,指向常量池中一个Utf8
,例子中值为00 06,是字符串I
,表明这个字段类型为int
。
attributes_count
是属性数量,值为00 00,表示没有属性。
方法表
000000b0 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 |................|
000000c0 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
方法表结构与字段表几乎一样。
methods_count
表示方法数量,在上面例子中值为00 02,即有两个方法,后面有两个method_info
项。
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法的access_flags
与字段有所不同。
标志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC |
0x0001 | public |
ACC_PRIVATE |
0x0002 | private |
ACC_PROTECTED |
0x0004 | protected |
ACC_STATIC |
0x0008 | static |
ACC_FINAL |
0x0010 | final |
ACC_SYNCHRONIZED |
0x0020 | synchronized |
ACC_BRIDGE |
0x0040 | 由编译器产生的桥接方法 |
ACC_VARARGS |
0x0080 | 方法接受不定参数 |
ACC_NATIVE |
0x0100 | native |
ACC_ABSTRACT |
0x0400 | abstract |
ACC_STRICT |
0x0800 | strictfp |
ACC_SYNTHETIC |
0x1000 | 方法由编译器生成 |
方法1
access_flags
值为00 01,表示是public方法。
name_index
值为00 07,指向字符串<init>
,这是方法名称。
descriptor_index
值为00 08,指向字符串()V
,表示方法没有参数,返回类型为void。
attributes_count
值为00 01,表示有1个attribute_info
。
attribute_info
结构如下,长度是变化的。
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
attribute_name_index
值为00 09
,指向字符串Code
。
attribute_length
值为00 00 00 1d即29,表明后面的info
字段长度为29字节。
000000c0 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
000000d0 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00 03 |................|
info
是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];
}
max_stack
值为00 01,表明操作数栈的深度为1。
max_locals
值为00 01,表明局部变量数量为1。
code_length
值为00 00 00 05,表明代码长度为5。
code
值为2a b7 00 01 b1,代表的指令分别为
- 2a
aload_0
- b7 00 01``invokespecial #00.#01`
- b1
return
exception_table_length
值为00 00,代表异常表为空,即不抛出异常。
attrbutes_count
值为00 01,表明属性表中有一个属性。
attribute_name_index
值为00 0a,指向字符串LineNumberTable
。LineNumberTable属性描述java源码和字节码的对应关系,它并不是运行时必备的属性,但默认会生成到class文件中。可以用javac -g:none
来禁用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
值为00 00 00 06,表明后面还有6字节。
line_number_table_length
值为00 01,表明line_number_table
中有一项。
这一项的start_pc
值为00 00,line_number
值为00 03,表明java源代码中行号为3的位置对应着class中该方法的起始位置。
方法2
000000e0 00 01 00 0b 00 08 00 01 00 09 00 00 00 27 00 03 |.............'..|
000000f0 00 01 00 00 00 0b 2a 59 b4 00 02 04 60 b5 00 02 |......*Y....`...|
00000100 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 |................|
00000110 06 00 0a 00 07 00 01 00 0c 00 00 00 02 00 0d |...............|
0000011f
access_flags
为00 01,表明是public方法。
name_index
值为00 0b,指向字符串inc
,这是方法名称。
descriptor_index
值为00 08,指向字符串()V
,表示方法没有参数,返回类型为void。
attributes_count
值为00 01,表示有1个attribute_info
。
attribute_name_index
值为00 09,指向字符串Code
,因此这个属性也是Code属性。
attribute_length
值为00 00 00 27即39,表明后面的info
字段长度为39字节。
max_stack
值为00 03,表明操作数栈的深度为3。
max_locals
值为00 01,表明局部变量数量为1。
code_length
值为00 00 00 0b,表明代码长度为11。
code
值为2a 59 b4 00 02 04 60 b5 00 02 b1,代表的指令分别为,显然这与inc
方法相符。
- 2a
aload_0
- 59
dup
- b4 00 02
getfield #00.#02
- 02
iconst_1
- 60
iadd
- b5 00 02
putfield #00.#02
- b1
return
exception_table_length
值为00 00,代表异常表为空,即不抛出异常。
attrbutes_count
值为00 01,表明属性表中有一个属性。
attribute_name_index
值为00 0a,指向字符串LineNumberTable
,表明是一个LineNumberTable属性。
attribute_length
值为00 00 00 0a,表明后面还有10字节。
line_number_table_length
值为00 02,表明line_number_table
中有2项。
第1项的start_pc
值为00 00,line_number
值为00 06,表明java源代码中行号为6的位置对应着code的起始位置。
第2项的start_pc
值为00 0a,line_number
值为00 07,表明java源代码中行号为7的位置对应着code第10字节的位置即return
。
属性表
00000110 06 00 0a 00 07 00 01 00 0c 00 00 00 02 00 0d |...............|
0000011f
attributes_count
值为00 01
,表明有一个属性。
attribute_name_index
值为00 0c,指向字符串SourceFile
,表明是一个SourceFile属性。其结构如下,占用8字节。
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_length
值为00 00 00 02,表明attribute_length
之后的属性内容一共占2字节。
sourcefile_index
值为00 0d,指向常量池中第13向,即字符串ConstantTest.java
。
至此,字节码文件中的每一个字节都解析完了!
总结
- 本文对class文件进行了逐字节分析,做了
javap
类似的工作,也得到了相符的结果。 - Cafe babe很有趣,Java的设计者们太会了!
- 各种定长结构体、变长结构体设计得非常巧妙,空间很紧凑,一点都没浪费。
- 常量池非常重要,字节码指令中包含对常量池的引用,常量池中的数据也有递归引用。
- 方法中的代码由一段Code属性描述,它就是一个字节数组。
- 构造方法的名称为
<init>
。
了解了class文件结构之后,就要开始探索类加载机制了。