class文件是为java程序精确定义的二进制文件格式. 正是因为这种精确的定义, 使得无论在任何平台或程序上产生的class文件都可以在其他平台的jvm上运行. 所以说class文件也没有那么神秘, 它的结构相对来说非常的固定. 每个class文件描述了一个单独的java类或接口.
所有类文件中的信息都以下面四种基本类型存储:
u1 | a single unsigned byte |
u2 | two unsigned bytes |
u4 | four unsigned bytes |
u8 | eight unsigned bytes |
class文件的主要组成部分, 按照出现的顺序, 如下面列表所示:
Type | Name | Count | ||
u4 | magic | 1 | ||
u2 | minor_version | 1 | ||
u2 | major_version | 1 | ||
u2 | constant_pool_count | 1 | ||
cp_info | constant_pool | constant_pool_count | - 1 | |
u2 | access_flags | 1 | ||
u2 | this_class | 1 | ||
u2 | super_class | 1 | ||
u2 | interfaces_count | 1 | ||
u2 | interfaces | interfaces_count | ||
u2 | fields_count | 1 | ||
field_info | fields | fields_count | ||
u2 | methods_count | 1 | ||
method_info | methods | methods_count | ||
u2 | attributes_count | 1 | ||
attribute_info | attributes | attributes_count |
魔术数字(Magic): 每个class文件的前四个字节都是这样的一个魔术数字:0xCAFEBABE,(Cafe baby, 明白了吧), 如果一个class文件不是以这四个字节开头, 那就不是一个合法的class文件, 这样很容易排除了粗制伪造的class.
大小版本号(minor_version and major_version): 大版本号和小版本号. 因为java技术在不断的进步发展, 许多新的特性会被加入到class文件中, 所以某个特定的jvm只能处理一定版本下的class, 同时会拒绝它不能处理的class.
常量池大小和常量池(constant_pool_count and constant_pool): 常量池包含了类或接口要引用到的常量, 包括字面量, final变量, 类名, 方法名,等等. 通常, 一个常量又需要通过下标引用到其他常量. 每个常量的开头都会有一个标志, 表明该常量的类型. 当虚拟机读到这个标志时, 就知道常量的具体类型了.一下列出这些常量的标志:
Entry Type | Tag Value | Description |
CONSTANT_Utf8 | 1 | A UTF-8 encoded Unicode string |
CONSTANT_Integer | 3 | An int literal value |
CONSTANT_Float | 4 | A float literal value |
CONSTANT_Long | 5 | A long literal value |
CONSTANT_Double | 6 | A double literal value |
CONSTANT_Class | 7 | A symbolic reference to a class or interface |
CONSTANT_String | 8 | A String literal value |
CONSTANT_Fieldref | 9 | A symbolic reference to a field |
CONSTANT_Methodref | 10 | A symbolic reference to a method declared in a class |
CONSTANT_InterfaceMethodref | 11 | A symbolic reference to a method declared in an interface |
CONSTANT_NameAndType | 12 | Part of a symbolic reference to a field or method |
常量池在动态链接中扮演重要角色, 除了上面的常量以外, 还有三种符号引用: 类或接口全名, 字段名和描述符, 方法名和描述符. 所有这些链接以符号的形式存在, 当jvm执行时, 需将这些符号引用替换成实际地址.
访问标志(Access Tag): 紧跟在常量池后面的两字节就是该类或接口的访问标志. 它可以是[ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_INTERFACE, ACC_ABSTRACT]的子集.
类名(this_class): 接下来两字节保存一个常量池的索引(因为类名是作为常量保存在常量池中的), 指向常量池中一个CONSTANT_CLASS_INFO.
父类(super_class): 跟随在类名后面, 也是一个指向常量池中的一个的索引. 当this_class为java.lang.Object时, super_class为0.
接口数量和接口(interfaces_count and interfaces): interfaces中以数组的形式保存一些常量池的索引.
字段数量和字段(fields_count and fields): 在field_count后面紧接着是以表格field_info的形式保存了字段信息. 字段和字段数量都只是本类或接口申明的,而不管父类. field_info的每行包含了字段名, 类型描述符, 修饰符, 如果字段申明为final, 则保存它在的常量池中的引用.
方法数量和方法(methods_count and methods): 结构同上. 在methods_count后面紧随method_info表. method_info表中包含了方法名, 修饰符, 返回类型, 参数类型, 非abstract方法还包含该方法的栈的字长, exceptions列表, 字节码序列.
属性数量和属性(attributes_count and attributes): 结构同上, 在attributes_count后面紧随attribute_info表. 属性来自不同的地方, 有一些来自虚拟机规范, 还有一些是实现者自己创建的. 但java虚拟机应该能够忽略那些它不认识的属性.