java虚拟机规范(se8)——class文件格式(一)
第四章 class文件格式
本章介绍了java虚拟机的class文件格式。每一个class文件包含一个单独的类或者接口的定义。虽然类和接口不一定都定义在文件中(比如类和接口亦可以通过类加载器直接生成),我们将通俗地将类或接口的任何有效表示称为class文件格式。class文件是由8位的字节流组成。所有16位,32位和64位的数字都是分别通过读取两个、四个和八个连续的8位字节来构成。多字节的数据使用大端模式存储的,也就是高字节在前。在java se平台,这个格式由接口java.io.DataInput和java.io.DataOutput以及例如java.io.DataInputStream和java.io.DataOutputStream等类来支持。
4.1 ClassFile结构
一个class文件是由单独的ClassFile结构组成:
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]; }
ClassFile结构中的项描述如下:
- magic
magic项定义了一个魔数来标识class文件格式,它的值是0xCAFEBABE。
- minor_version,major_version
minor_version和major_version的值分别表示class文件次版本号和主版本号。次版本号和主版本号一起决定了class文件的版本号。如果一个class文件的主版本号是M,次版本号是m,我们成这个class文件的版本号是M.m。因此类文件格式版本号可以按字典序排序,例如,1.5<2.0<2.1。
当且仅当v位于某个连续范围Mi.0≤v≤Mj.m时(Mi.0到Mj.m为虚拟机支持的版本号),Java虚拟机实现可以支持版本v的类文件格式。 Java虚拟机所遵循的Java SE平台的发行版级别负责确定范围。
JDK 1.0.2版中的Oracle Java虚拟机实现支持包含45.0到45.3类的类文件格式。 JDK1.1.*版本支持类文件格式版本范围为45.0到45.65535(含)。 对于k≥2,JDK版本1.k支持45.0到44 + k.0范围内的类文件格式版本。
- constant_pool_count
constant_pool_count的值为constant_pool表中条目的数量+1。一个constant_pool索引大于0小于constant_pool_count才是有效的,除了long和double类型的常量,具体见4.4.5。
- constant_pool[]
constant_pool是一个表结构,表示各种字符串常量,类和接口名称,字段名称以及在ClassFile结构及其子结构中引用的其他常量。
constant_pool表的索引从1到constant_pool_count_1。
- access_flags
access_flags的值是掩码标识,表示这个类或者接口访问权限和属性。每个标识被设置时的含义如下表4.1A
表4.1A 访问和修饰符标识
标识名称 | 值 | 含义 |
---|---|---|
ACC_PUBLIC |
0x0001 | 声明为 public ; 可以被包以外的类访问. |
ACC_FINAL |
0x0010 | 声明为 final ; 不允许有子类. |
ACC_SUPER |
0x0020 | 当调用invokspecialial指令时,要特殊处理超类方法。 |
ACC_INTERFACE |
0x0200 | 是一个接口,不是一个类 |
ACC_ABSTRACT |
0x0400 | 声明为 abstract ; 不能被实例化. |
ACC_SYNTHETIC |
0x1000 | 声明为 synthetic; 源代码中不存在. |
ACC_ANNOTATION |
0x2000 | 是一个注解类型. |
ACC_ENUM |
0x4000 | 是一个枚举类型. |
接口是通过设置ACC_INTERFACE标识来区分,如果这个标识没有被设置,那么这个class文件就是定义了一个类,而不是接口
如果ACC_INTERFACE标识被设置了,那么ACC_ABSTRACT标识也必须被设置,ACC_FINAL,ACC_SUPER和ACC_ENUM表示不允许被设置。
如果ACC_INTERFACE标识未被设置,那么其它的标识除了ACC_ANNOTATION都可以被设置。然而这样的class文件不能同时设置ACC_FINAL和ACC_ABSTRACT。
ACC_SUPER标志指示如果它出现在此类或接口中,则由invokespecial指令(§invokespecial)表示两个备用语义中的哪一个。 Java虚拟机指令集的编译器应设置ACC_SUPER标志。 在Java SE 8及更高版本中,Java虚拟机认为要在每个类文件中设置ACC_SUPER标志,而不管类文件中的标志的实际值和类文件的版本。
ACC_SUPER标志的存在是为了向后兼容由较老的Java编程语言编译器编译的代码。在JDK 1.0.2之前的版本中,编译器生成了访问标志,其中现在表示ACC_SUPER的标志没有指定的含义,如果设置了该标志,Oracle的Java虚拟机实现将忽略该标志。
ACC_SYNTHETIC标识表示这个类或者接口是由编译器产生的并且没有出现在原码中。
注解类型必须设置ACC_ANNOTATION标识,如果ACC_ANNOTATION标识被设置了,那么ACC_INTERFACE标识也必须被设置。
ACC_ENUM标识表示这个类或者它的父类被声明为枚举类型。
access_flags中所有未在表4.1A中分配的位都被保留给未来使用。当生产class文件的时候,它们应该被设置为0,并且java虚拟机实现需要忽略它们。
- this_class
this_class的值必须是constant_pool表的有效索引。constant_pool在这个索引位置的条目必须是一个CONSTAN_Class_info结构,表示这个class文件定义的类或者接口。
- super_class
对于类来说,super_class的值必须是0或者constant_pool的有效索引。如果super_class的值不是0,constant_pool在这个索引位置的条目必须是一个CONSTANT_Class_info结构,表示这个class文件定义的类的直接父类。它的直接父类以及它的所有父类的ClassFile结构中的access_flags都不能设置ACC_FINAL标识。
如果super_class的值是0,那么这个类必须是Object类,这是唯一没有直接支持父类的类或者接口。
对于接口,super_class的值必须是constan_pool的有效索引,这个索引对应的条目必须是一个CONSTAN_Class_info结构,代表Object类。
- interfaces_count
interfaces_count的值表示这个类或者接口的直接父接口的数量
- interfaces[]
interfaces数组的每一个值必须是constant_pool的一个有效索引。constant_pool中interfaces[i]索引值对应的条目必须是一个CONSTANT_Class_info结构,表示这个类或者接口的直接父接口,并且按照原码中从左到有的顺序给出。
- fields_count
fields_count的值表示fields表中field_info结构的数量。field_info结构表示这个类或者接口声明的所有字段,包括类变量和实例变量。
- fields[]
fields表中的每一个值必须是一个field_indo结构,给出了这个类或者接口中一个字段的完整描述。fields表仅仅包含声明在这个类或者接口中的字段。不包括继承自父类或者父接口的字段。
- methods_count
methods_count的值给出类methods表中method_info结构的数量
- methods[]
methods表中的每一个值必须是一个method_info结构,method_info给出了这个类或者接口中一个方法的完整描述。如果metchod_info结构中access_flags的ACC_NATIVE和ACC_ABSTRACH标识都没有设置,那么实现了这个方法的java虚拟机指令也会提供。
method_info结构表示了这个类或者接口的所有方法,包括实例方法,类方法,实例初始化方法以及任何的类和接口的初始化方法。methods表不包含继承自父类或者父接口的方法。
- atttibutes_count
attribute_count的值给出了这个类中attributes表中attrubute的数量。
- attributes[]
attributes表中的每一个值都是attribute_info结构。
表4.7-C列出了本规范定义的出现在类文件结构的属性表中的属性。
有关定义出现在ClassFile结构的属性表中的属性的规则在§4.7中给出。
有关ClassFile结构的属性表中的非预定义属性的规则在§4.7.1中给出。
4.2 名称的内部表示
4.2.1 二进制类和接口的名称
出现在class文件结构中的类和接口名称始终以称为二进制名称(binary names)的完全限定形式表示(JLS§13.1)。这些名称始终表示为CONSTANT_Utf8_info结构(第4.4.7节),因此可以从整个Unicode字符空间中获取(如果没有进一步的约束)。类和接口的二进制名称还会被CONSTANT_NameAndType_info结构所引用,用于构成它们的描述符(4.3),引用这些名称通过引用它们的CONSTAN_Class_info结构来实现。
由于历史原因,出现在类文件结构中的二进制名称的语法与JLS§13.1中记录的二进制名称的语法不同。 在此内部形式中,通常将构成二进制名称的标识符分隔的ASCII句点(.)替换为ASCII正斜杠(/)。 标识符本身必须是非限定名称(第4.2.2节)。
例如,Thread类的普通二进制名称是java.lang.Thread。 在类文件格式的描述符中使用的内部形式中,使用表示字符串java/lang/Thread的CONSTANT_Utf8_info结构来实现对类Thread的名称的引用。
4.2.2 非限定名称
方法,字段,局部变量和形参的名称使用非限定名称(unqualified names)存储。非全限定名中必须至少包含一个Unicode代码点,并且不得包含ASCII字符. ; [ /(也就是点号,分号,左方括号,正斜杠)。
方法名称进一步受到限制,除了特殊方法名称<init>和<clinit>(§2.9)之外,它们不能包含ASCII字符<或>(即左尖括号或右尖括号) 。
请注意,字段名称或接口方法名称可以是<init>或<clinit>,但是没有方法调用指令可以引用<clinit>,只有invokespecial指令(§invokespecial)可以引用<init>。
4.3 描述符
描述符是表示字段或者方法类型的字符串。描述符使用改进的UTF-8字符串(第4.4.7节)以class文件格式表示,因此可以使用整个Unicode字符空间中的字符(如果没有进一步限制的话)。
4.3.1 语法符号
描述符使用特定的语法来表示。语法是一组描述了字符序列如何形成各种语法正确的描述符的产物。语法中的终止符使用固定宽度的字体,非终止符使用斜体。非终止符的定义由被定义的非终止名后跟随一个冒号表示。冒号右侧的一个或多个可交换的非终止符连续排列。
出现在右侧的{x}表示出现0次或多次。
右侧的短语(one of)表示下面一行或者多行上的每个终止符都是可选的定义。
4.3.2 字段描述符
一个字段描述符表示一个类,实例或者局部变量的类型。
FieldDescriptor:
FieldType
FieldType:
BaseType
ObjectType
ArrayType
BaseType:
(one of)
B C D F I J S Z
ObjectType:
L ClassName ;
ArrayType:
[ ComponentType
ComponentType:
FieldType
BaseType的各个字符,ObjecType的 "L "和 "; "以及ArrayType的 "[" 都是ASCII字符。
ClassName表示一个二进制类或者接口的内部形式的名称。
字段描述符的类型含义见表4.3-A
表示数组类型的字段描述符仅在表示具有255或更少维度的类型时才有效。
表4.3-A 字段描述符的含义
字段类型项 | 类型 | 含义 |
---|---|---|
B |
byte |
signed byte |
C |
char |
Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D |
double |
double-precision floating-point value |
F |
float |
single-precision floating-point value |
I |
int |
integer |
J |
long |
long integer |
L ClassName ; |
reference |
an instance of class ClassName |
S |
short |
signed short |
Z |
boolean |
true or false |
[ |
reference |
one array dimension |
int类型的实例变量的字段描述符就是I.
Object类型的实例变量的字段描述符是Ljava/lang/Object;。 请注意,使用类Object的二进制名称的内部形式。
多维数组类型double [] [] []的实例变量的字段描述符是[[[D.
4.3.3 方法描述符
方法描述符包含零个或多个参数描述符,表示方法采用的参数类型,以及返回描述符,表示方法返回的值的类型(如果有)。
MethodDescriptor:
( {ParameterDescriptor} ) ReturnDescriptor
ParameterDescriptor:
FieldType
ReturnDescriptor:
FieldType
VoidDescriptor
VoidDescriptor:
V
符号V表示方法没有返回值(它的结果是void)。
这个方法的描述符: Object m(int i, double d, Thread t) {...} 是: (IDLjava/lang/Thread;)Ljava/lang/Object; 注意,使用了Thread和Objec的二进制名称的内部形式。
如果一个方法描述符是有效的,那么它对应的方法的参数列表总长度小于255,对于实力方法和接口方法,需要考虑额外的this。参数列表长度的计算规则如下:每个long和double类参数长度为2,其余为1,方法参数列表的总长度等于所有参数长度之和。
无论方法描述的方法是类方法还是实例方法,方法描述符都是相同的。 虽然实例方法被传递了this,指向被调用方法的对象的引用,除了它的预期参数之外,该事实不会反映在方法描述符中。 this的引用由调用实例方法的Java虚拟机指令隐式传递(§2.6.1,§4.11)。