1. ClassFile 基本定义
Classfile 是一个连续的8 位字节二进制流,数据项按照顺序存储在class 文件中,相邻项没有间隔,占多字节空间的项时,高位在前。
ClassFile 文件格式是固定的,按照顺序
名称 |
长度 |
描述 |
备注 |
majic |
4 个字节 |
魔数 :0xCAFEBABE |
Od –x 命令可以看到。这样保证了 Java 虚拟机能很轻松的分辨出 Java 文件和非 Java 文件 |
Minor_version 和 major_version |
分别 2 字节 |
主次版本号: Class 文件格式一旦发生变化,版本号也会随之变化。 |
如果 class 文件版本号超出了处理范围, java 虚拟机将不会处理该文件。 |
Constantpool_count,constanpool |
不固定 |
常量池:包含了文件中类和接口相 关的常量。文字字符串、 final 变量值、类名和方法名的常量。常量池的大小平均占到了整个类大小的 60% 左右。 |
入口列表的形式来存储。每个常量 池入口都从一个长度为一个字节的标志开始。除了字面常量还可以容纳字段名称、方法名称和类的全限名等。 |
Access_flags |
2 字节 |
访问标志 : 定义了类或接口 |
指明了是类还是接口、是抽象还是 具体。公共、 final 等修饰符。 |
This_class |
2 字节 |
本身是一个常量池的索引,指向了 常量池中该类全限定名的常量池入口 |
|
Super_class |
2 字节 |
指向父类全限定名 |
|
Interface_count 和 interfaces |
不固定 |
该类实现的接口数量, interfaces 包含了由该类实现的接口的常量池引用。 |
|
FiledsCount 和 fileds |
不固定 |
字段数量和字段的信息表。描述了 字段的类型、描述符等。 |
|
Methods_count 和 Mechods |
不固定 |
方法总数和方法本身。使用 ASM 进行 AOP 编程,通常是通过调整 Method 中的指令来实现的。 |
每一个方法都会有一个 Mechod_info 表,改表记录了方法的方法名、描述符、返回类型。局部变量表,字节码序列等。 |
Attributes_count 和 Attributes |
不固定 |
属性总数和属性本身。写出了 |
|
2.class文件的结构
The ClassFile Structure
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 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[attrributes_count];
}
说明:u2表示无符号2字节量,u4表示无符号4字节量。
各字段的说明:
magic
一般二进制文件中都有magic number这一项,这个数说明该文件是一个class文件,它的值是0xcAFEBABE。
minor_version,major_version
class文件的版本号。
constant_pool_count
常量池表中项目数加1得到。从后面的cp_info constant_pool[constant_pool_count-1];也容易理解这个值的意思。
cp_info constant_pool[]
存储该class文件中使用到的常量信息。
access_flag
说明该class文件的访问权限和相关属性,通过位掩码来设定。
Flag Name |
值 |
解释 |
ACC_PUBLIC |
0x0001 |
public |
ACC_FINAL |
0x0010 |
final |
ACC_SUPER |
0x0020 |
跟调用invokespecial指令时的处理有关 |
ACC_INTERFACE |
0x0200 |
这是个接口 |
ACC_ABSTRACT |
0x0400 |
abastract |
ACC_SYNTHETIC |
0x1000 |
没有在源代码中出现,该类是编译器生成的 |
ACC_ANNOTATION |
0x2000 |
注解类型 |
ACC_ENUM |
0x4000 |
枚举类型 |
这些属性都很容易理解,比如我们定义一个类public final Test{},则Test.class的access_flag中,ACC_PUBLIC和ACC_FINAL是置位的,而ACC_INTERFACE一 项肯定是0。很多类似的组合规则倒是容易自己总结出来,比如如果ACC_INTERFACE置位了,那肯定ACC_ABSTRACT也是置位的,因为接口 肯定是抽象的;如果ACC_ANNOTATION置位了,那ACC_ANNOTATION肯定同时置位,等等,诸如此类。
画一个具体的位示意图表示:
this_class
一个下标,指向常量池表中代表当前类的Constant_Class_info。
super_class
为0,或者为一个下标,指向常量池中代表其父类的Constant_Class_info。如果这个值为0,那么当前类肯定是Object类。如果 当前类是一个接口,该下标指向的是Object类的描述信息。
interface_count
该类实现的接口数,或者该接口实现的超接口数。
interfaces[]
具体的接口信息,每一项都是常量池表中的一个下标,指向表示接口的Constant_Class_info。
后面几个依次是字段(field),方法(methods)和属性的描述,不再赘述,
下面具体的分析一个class文件。源文件很简单:
public class Test{
public static void main(String[] args){
System.out.println("Hello World!");
}
}
编译,得到Test.class文件,使用WinHex打开。
前4个字节是magic部分,内容为0xCAFEBABE;随后的4个字节是class文件的版本号,这里主版本号是0x0032,也就是50,这 是使用jdk1.6编译出的class文件。再后面的2个字节是常量池表的大小信息,0x001D,也就是29,说明常量池表中一共有28项。为了方便观 察,可以使用javap -verbose Test得到的结果和当前的二进制文件进行对比。