Java class文件是8位字节的二进制流。数据项按顺序存储在class文件中,相邻的项之间没有任何间隔,这样可以使class文件紧凑。占据多个字节空间的项按照高位在前的顺序分为几个连续的字节存放。和java的类可以包含多个不同的字段、方法、方法参数、局部变量等一样,Java class文件也能够包含许多不同大小的项。在class文件中,可变长度项的大小和长度位于其实际数据之前。这个特性使得class文件流可以从头到尾被顺序解析,首先读出项的大小,然后读出项的数据。
1.class文件内容
1.1 java class文件基本类型
u1 | 1个字节,无符号类型 |
---|---|
u2 | 2个字节,无符号类型 |
u4 | 4个字节,无符号类型 |
u8 | 8个字节,无符号类型 |
1.2 可变长度的ClassFile表中的项,如下表:
类型 | 名称 | 数量 |
---|---|---|
u4 | magic | 1 |
minor_version | 1 | |
major_version | 1 | |
constant_pool_count | 1 | |
cp_info | constant_pool | constant_pool_count-1 |
u2 | access_flags | 1 |
this_class | 1 | |
super_class | 1 | |
interfaces_count | 1 | |
interfaces | interfaces_count | |
fields_count | 1 | |
field_info | fields | fields_count |
methods_count | 1 | |
methods | 1 | |
attributes_count | 1 | |
attribute_info | attributes | attributes_count |
1.3ClassFile表中各项含义如下:
1.3.1magic(魔数)
每个java class文件的前4个字节被成为它的魔数:0XCAFEBABE 。魔数的作用在于,可以轻松的分辨出java class文件和非java class文件。如果一个文件不是以0XCAFEBABE开头,那么它就肯定不是java class文件。文件格式定义者能够自由选择魔数,前提是这个选定的魔数值没有被广泛引用。
1.3.2 minor_version和major_version
class 文件的下面4个字节包含了主、次版本号。对于Java虚拟机来说,版本号确定了特定的class文件格式,通常只有给定主版本号和一系列次版本号后,java虚拟机才能读取class文件。如果class文件的版本号超出了java虚拟机所能处理的有效范围,Java虚拟机将不会处理该class文件。
1.3.3constant_pool_count和constant_pool
在class文件中,魔数和版本号后面的是常量池。常量池包含了与文件中类和接口相关的常量。常量池中存储了诸如文字字符串,final变量值、类名和方法名的常量。Java虚拟机把常量池组织为入口列表的形式。在实际列表constant_pool之前,是入口在列表着哦功能的计数constant_pool_count。
常量池中的许多入口都指向其他的常量池入口,而且class文件中紧随着常量池的许多条目也会指向常量池中的入口。在整个class文件中,指示常量池入口在常量池列表中位置的整数索引都指向这些常量池入口。列表着哦功能的第一项索引值为1,第二项索引值为2,依此类推。尽管constant_pool列表中没有索引值为0的入口,但缺失的这一入口也被constant_pool_count计数在内。例如,当constant_pool中一14项(索引值从1到14时),constant_pool_count的值为15.
每个常量池入口都从一个长度为一个字节的标识开始,这个标识指出了列表中该位置的常量类型。一旦java虚拟机获取并解析这个标识,java虚拟机就会知道在标识后的常量类型是什么。
常量池标识
入口类型 | 标识值 | 描述 |
---|---|---|
CONSTANT——Utf-8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT——Integer | 3 | int类型字面值 |
4 | float类型字面值 | |
5 | long类型字面值 | |
CONSTANT_Double | 6 | double类型字面值 |
7 | 对一个类或接口的符号引用 | |
CONSTANT_String | 8 | String类型字面值 |
9 | 对一个字段的符号引用 | |
10 | 对一个类中声明的方法的符号引用 | |
CONSTANT_InterfaceMethodref | 11 | 对一个接口中声明的方法的符号引用 |
CONSTANT_NameAndType | 12 | 对一个字段或方法的部分符号引用 |
表中的每个标识都有一个相对应的表,表名通过在标识名后加上“_info”后缀来产生。例如,对应于CONSTANT_Class标识的表名为CONSTANT_Class_info,表名为CONSTANT_Utf8_info的表中存储着Unicode字符串的压缩形式。
在动态链接的Java程序中,常量池充当了十分重要的角色。除了字面常量(或者说直接量)值以外,常量池还可以容纳下面几种符号引用:
(1)、类和接口的全限定名;
(2)、字段的名称和描述符;
(3)、方法的名称和描述符;
字段是类或接口的实例变量或者类变量。字段的描述符是一个指示字段的类型的字符串。方法的描述符也是一个字符串,该字符串指示方法的返回值和参数的数量、顺序和类型。在运行时,Java虚拟机使用常量池的全限定名、方法和字段的描述符,把当前类或接口中的代码与其他接口中的代码链接起来。由于class文件并不包含其内部组建最终内存布局的信息,因此类、字段和方法并不能被class文件中的字节码直接引用。Java虚拟机从常量池获得符号引用,然后在运行时解析引用项的实际地址。例如,用来调用方法的字节码指令把一个符号引用的常量池索引传给所调用的方法。
1.3.4access_flags
紧接常量池后的两个字节称为access_flags,它展示了文件中定义的类或接口的几段信息。
ClassFile表内access_flags项的标识位
标识名 | 值 | 设置后的含义 | 设置者 |
---|---|---|---|
0X0001 | public类型 | 类和接口 | |
0X0010 | 类为final类型 | 只有类 | |
0X0020 | 使用新型的invokespecial语义 | 类和接口 | |
0X0200 | 接口类型,不是类类型 | 所有的接口,没有类 | |
0X0400 | abstract类型 | 所有的接口,部分类 |
在access_flags中所有未使用的位都必须由编译器置为0,而且Java虚拟机必须忽略它。
1.3.5this_class
接下来的两个字节为this_class项,它是一个对常量池的索引。在this_class位置的常量池入口必须为CONSTANT_Class_info表。该表由两个部分组成——标签和name_index。标签部分是一个具有CONSTANT_Class值的常量,在name_index位置的常量池入口为一个包含子类或接口全限定名的CONSTANT_Utf8_info表。
this_class项提供了一个如何使用常量池的范例。对于它自身来说,this_class项只是一个指向常量池的索引。当java虚拟机在this_class位置查阅常量池的入口的时候,它会发现一个通过把自己的标签设为CONSTANT_Class来识别自身的项,Java虚拟机知道,在CONSTANT_Class_info入口中,标签的后面总是会有一个名为name_index的、指向常量池的索引,于是虚拟机在name_index位置查找常量池入口,在这个位置,Java虚拟机应该能找到一个容纳了类或者接口全限定名的CONSTANT_Utf8_info入口。对于这个过程,如下图示描述:
1.3.6super_class
在class文件中,紧接在this_class之后的是super_class项,它是一个两个字节的常量池索引,在super_class位置的常量池入口是一个指向该类超类全限定名的CONSTANT_Class_info入口,因为Java程序中所有对象的基类都是java.lang.Object类,除了Object类以外,常量池索引super_class对于所有的类均有效。对于Object类,super_class的值为0,对于接口,在常量池入口super_class位置的项为java.lang.Object。
1.3.7interfaces_count和interfaces
紧接着super_class的是interfaces_count。此项的含义为:在文件中由该类直接实现或由接口所扩展的父接口的数量。在这个计数的后面,是名为interfaces的数组,它包含了对每个由该类或者接口直接实现的父接口的常量池索引。每个父接口都使用一个常量池中的CONSTANT_Class_info入口来描述,该CONSTANT_Class_info入口指向接口的全限定名。这个数组只容纳哪些直接出现在类声明的implements子句或者接口声明的extends子句中的父接口。超类按照在implements子句和extends子句中出现的顺序(从左到右)在这个数组中显现。
1.3.8fields_count和fields
在class文件中,紧接在interfaces后面的是对在该类或者接口中声明的字段的描述。首先是名为fields_count的计数,它是类变量和实例变量的字段的数量总和。在这个计数后面的是不同长度的field_info表的序列(fields_count指出了序列中有多少个field_info表)。只有在文件中由类或者接口声明了的字段才能在fields列表中列出。在fields列表中,不列出从超类或者父接口继承而来的字段。另一方面,fields列表可能会包含在对应的java源文件中没有叙述的字段,这时因为java编译器可能会在编译时向类或接口添加字段。例如,对于一个内部类的fields列表来说,为了保持对外围类实例的引用,java编译器会为每个外围类实例添加实例变量。源代码中并没有叙述任何在fields列表中的字段,它们是被java编译器在编译时添加进去的,这些字段使用synthetic属性标识。
每个field_info表都展示了一个字段的信息。此表包含了字段的名字、描述符和修饰符。如果该字段被声明为final、field_info表还会展示其常量值。这样的信息有些放在field_info表中,有些则放在由field_info表所指向的常量池中。
1.3.9methods_count和methods
在class文件中,紧接着fields后面的是对在该类或者接口中所声明的方法的描述。首先是名为methods_count的计数,它是一个双字节长度的对于该类或者接口中声明的所有方法的总计数。这个总计数只包括在该类或者接口中显式定义的方法(从超类或者父接口中继承来的方法不被计入)。在methods_count后面的是方法本身,它在一个method_info表的列表中进行了阐述(methods_count指出了列表中有多少个method_info表)。
method_info表中包含了与方法相关的一些信息,包括方法名和描述符(方法的返回值类型和参数类型)。如果方法既不是抽象的,又不是本地的,那么method_info表就包含局部变量所需的栈空间长度、为方法所捕获的异常表、字节吗序列以及可选的行数和局部变量标。如果方法能够抛出任何已验证的异常,那么method_info表就会包括一个关于这些已验证异常的列表。
1.3.10attributes_count和attributes
class文件中最后的部分是属性(attribute),它给出了在该文件中类或者接口所定义的属性的基本信息。属性部分由attriutes_count开始,attributes_count是指出现在后续attributes列表中的attribute_info表的数量总和。每个attribute_info的第一项是指向常量池中CONSTANT_Utf8_info表的索引,该表给出了属性的名称。
属性有许多种。java虚拟机规范定义了几种属性,但任何人都可以创建他们自己的属性种类(通过特定的规则),并且把它们置于class文件中。java虚拟机实现必须忽略任何不能识别的属性。属性出现在class文件中的多处,而不仅仅在顶层ClassFile表的attributes项中出现。出现在ClasssFile表中的属性主要给出了与文件中所定义的类和接口相关的信息;出现在field_info表中的属性主要给出了与字段相关的信息;出现在method_info表中的属性主要给出了方法相关的信息。
其中java虚拟机实现定义了两种属性:SourceCode和InnerClasses。