这里参考的资料为《java虚拟机规范Java SE 7》,大家有兴趣的话可以点此下载pdf版本,感谢原作者的汉化以及提供下载。(注:译者之一的:周志明先生在Java虚拟机方面很有研究)
下面开始正题,
要点:
1.class文件是以8位字节流格式组织的
2.如果对于大于一个字节的数据类型,那么class文件是以Big-Endian形式存储的
3.题外话:在java中,读取class文件的数据可以使用java.io.DataInput和Java.io.DataOutput,Java.io.DataInputStream和java.io.DataOutputStream等类来实现
4.下面将以C语言中结构的形式给出class文件的格式
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];
}
可以看到,基本上都是“ux”类型的变量,这里的“ux”表示“x个字节的无符号数”,我们从上面可以发现有4个字段不是“ux”类型的,他们分别是 cp_info, field_info, method_info, attribute_info。
好的,下面就开始解析这些字段的意思
u4 magic:
这四个字节是class文件的“魔数”,无论是windows还是linux里面,文件的开头都有“魔数”这个概念,可以帮助我们了解这个文件是什么文件,class文件的“魔数”是“0xCAFEBABE”。
u2 minor_version
u2 major_version
这四个字节是用来描述该class文件的版本信息的,minor_version描述的是副版本信息,而major_version描述的是主版本信息,共同构成class文件的格式版本号。
一个虚拟机只支持特定版本号的class文件,这个版本号的范围为Mi.0 <= 支持 <=Mx.m.。
u2 constant_pool_count
常量池计数器,这个值是给下一个字段使用的
cp_info constant_pool[constant_pool_count - 1]
常量池,constant_pool是一个表结构,它存储了 constant_pool_count - 1 个成员,它包含了class文件结构和子结构引用的所有字符串常量,类和接口名,字段名,其他常量。常量池中的每一项都具备相同的格式特征-第一个字符作为类型标记用于识别该项是哪种类型的常量,称为“tag byte”。常量池的索引范围是 1 至 constant_pool_count−1
标记名 | 值 | 含义 |
ACC_PUBLIC | 0x0001 | 可以被包的类外访问 |
ACC_FINAL | 0x0010 | 不允许有子类 |
ACC_SUPER | 0x0020 | 当用到 invokespecial 指令时,需要特殊处理的父类方法 |
ACC_INTERFACE | 0x0200 | 标识定义的是接口而不是类 |
ACC_ABSTRACT | 0x0400 | 不能被实例化 |
ACC_SYNTHETIC | 0x1000 | 标识并非Java源码生成的代码 |
ACC_ANNOTATION | 0x2000 | 标识注解类型 |
ACC_ENUM | 0x4000 | 表示枚举类型 |
- 带有 ACC_SYNTHETIC 标志的类,意味着它是由编译器自己产生的而不是由程序员编写的源代码生成的。
- 带有 ACC_ENUM 标志的类,意味着它或它的父类被声明为枚举类型。
- 带有 ACC_INTERFACE 标志的类,意味着它是接口而不是类,反之是类而不是接口。如果一个 Class 文件被设置了 ACC_INTERFACE 标志,那么同时也得设置ACC_ABSTRACT 标志。同时它不能再设置 ACC_FINAL、ACC_SUPER 和 ACC_ENUM 标志。
- 注解类型必定带有 ACC_ANNOTATION 标记,如果设置了 ANNOTATION 标记,ACC_INTERFACE 也必须被同时设置。如果没有同时设置 ACC_INTERFACE 标记,那么这个 Class 文件可以具有表 4.1 中的除 ACC_ANNOTATION 外的所有其它标记。当然 ACC_FINAL 和 ACC_ABSTRACT 这类互斥的标记除外
- ACC_SUPER 标志用于确定该 Class 文件里面的 invokespecial 指令使用的是哪一种执行语义。目前 Java 虚拟机的编译器都应当设置这个标志。ACC_SUPER 标记是为了向后兼容旧编译器编译的 Class 文件而存在的, JDK1.0.2 版本以前的编译器产生的 Class 文件中,access_flag 里面没有 ACC_SUPER 标志。同时,在JDK1.0.2 前的 Java 虚拟机遇到 ACC_SUPER 标记会自动忽略它。
- 在表 4.1 中没有使用的 access_flags 标志位是为未来扩充而预留的,这些预留的标志为在编译器中会被设置为 0, Java 虚拟机实现也会自动忽略它们
u2 this_class
类索引,this_class的值是对 constant_pool 表中项目的引用,表示这个class文件所定义的类或接口
u2 super_class
父类索引,对于类来说,这个引用必须是0或者对 constant_pool 的一个有效引用,表示这个class文件所定义的类的直接父类。对于接口来说,它的class文件的 super_class 的值必须是对 constant_pool 的一个有效索引,constant_pool在这个索引处的项必须是代表java.lang.Object的数据类型。如果 super_class 的值为0,那么这个class文件只能为定义java.lang.Object类了,因为,只有这个类是没有父类的。
u2 interface_count
被下面一个字段使用,表示当前类或接口的直接父接口数量
u2 interfaces[interfaces_count]
接口表,每一项都表示对 constant_pool 的一项引用,长度为 interface_count ,在interfaces数组中,从0到interfaces_count - 1的数量代表着源代码中继承接口从左到右的顺序
u2 fields_count
字段计数器,给下面的字段使用
field_info fields[fields_count]
字段表,每一个成员都是 filelds_info 类型,表示当前类或接口中某个字段的完整描述,fileds数组描述当前类或接口的所有字段,但不包括从父类或父接口继承的部分。
u2 methods_count
方法计数器,给下面的字段使用
method_info methods[methods_count]
方法表,表示当前类或接口中某个方法的完整描述,如果某个 method_info 结构的 access_flags 项既没有设置 ACC_NATIVE 标志也没有设置 ACC_ABSTRACT 标志,那么它所对应的方法体就应当可以被Java 虚拟机直接从当前类加载,而不需要引用其它类。method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法方法和类或接口初始化方法方法
methods数组只描述当前类或接口中声明的方法,不包括父类或者接口继承的方法
u2 attributes_count
属性计数器,给下面的字段使用
attribute_info attributes[attributes_count]
属性表,Class 文件结构中的 attributes 表的项包括下列定义的属性:InnerClasses、EnclosingMethod、Synthetic、Signature、SourceFile,SourceDebugExtension、Deprecated、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations以及BootstrapMethods属性。对于支持 Class 文件格式版本号为 49.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的 Signature、RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations属性。对于支持 Class 文件格式版本号为 51.0 或更高的 Java 虚拟机实现,必须正确识别并读取 attributes 表中的BootstrapMethods属性
转至http://www.liuliqiang.com/post-180.html