The Java Virtual Machine Specification, Java SE 8
本文介绍了class文件格式、用于表示已编译的类和接口的硬件和操作系统相关的二进制格式。
目录
一、ClassFile结构(The ClassFile Structure)
二、名称的内部形式(The Internal Form of Names)
九、对Java虚拟机代码的约束(Constraints on Java Virtual Machine Code)
十、class文件的验证(Verification of class Files)
十一、Java虚拟机的限制(Limitations of the Java Virtual Machine)
介绍
本章介绍了Java虚拟机的 class文件格式。每个 class文件都包含单个类或接口的定义。尽管类或接口不需要有一个真正包含在文件中的外部表示(例如,因为类是由类加载器生成的),但我们将把类或接口的任何有效表示称为 class文件格式。
一个class文件由一个8位字节的流组成。所有16位、32位和64位的数目都是通过分别通过读取2、4和8个连续的8位字节来构造的。多字节数据项总是以大端顺序存储,其中高字节先出现。
在Java SE平台中,接口 java.io.DataInput和 java.io.DataOutput 和 类似 java.io.DataInputStream 和 java.io.DataOutputStream的classes类都支持这种格式。
本章定义了表示 class文件数据的数据类型集:类型u1、u2和u4分别表示无符号的一字节、二字节或四字节的数量。在Java SE平台中,这些类型可以通过类似 readUnsignedByte、readUnsignedShort 和 java.io.DataInput接口的 readInt方法读取。
本章介绍了使用类C结构符号编写的伪结构的 class文件格式。为了避免与类和类实例等字段的混淆,描述 class文件格式的结构的内容被称为项 items。连续的项按顺序存储在 class文件中,没有填充或对齐。
Tables由零个或多个可变大小的项组成,在多个 class文件结构中使用。虽然我们使用类似C的数组语法来引用表项,但表是不同大小的结构流,这意味着不可能将表索引直接转换为字节偏移量到表中。
我们将数据结构称为 array数组,它由0个或多个连续的固定大小的项目组成,可以像数组一样进行索引。
本章中对ASCII字符的引用应解释为与ASCII字符对应的Unicode代码点。
一、ClassFile结构(The ClassFile Structure)
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文件格式的 magic数字;它的值为0xCAFEBABE。
minor_version, major_version
minor_version 和 major_version项的值是这个 class文件的次要版本号和主要版本号。
主版本号和次要版本号一起决定了 class文件格式的版本。
如果一个 class文件有主版本号M和小版本号M,我们将其 class文件格式的版本表示为M.m。
因此,class文件格式版本可以按字典顺序排序,例如,1.5<2.0<2.1。
Java虚拟机实现可以支持版本v的类文件格式,当且仅当v位于某个连续范围Mi.0 ≤ v ≤ Mj.m.
Java虚拟机实现所符合的Java SE平台的发布级别负责确定其范围。
Oracle在JDK1.0.2版本中的Java虚拟机实现支持 class文件格式版本45.0到45.3。
JDK发布了1.1.*支持包含45.0到45.65535范围内的 class文件格式版本。
对于k≥2,JDK版本1.k支持包含45.0到44+k.0范围内的 class文件格式版本。
constant_pool_count 常量池计数
constant_pool_count项的值等于 constant_pool表中的条目数加上1。
如果 constant_pool索引大于0且小于constant_pool_count,则认为它有效,但4.4.5中指出的long型和double型常数除外。
constant_pool[] 常量池数组
constand_pool是一个结构表(4.4),表示各种类字符串常量、类和接口名、字段名称以及ClassFile结构及其子结构中引用的其他常量。每个constant_pool表项的格式由它的第一个“标记”字节表示。
constant_pool表被索引为constant_pool_count - 1。
access_flags 修饰符
access_flags项的值是用于表示对此类或接口的访问权限和属性的标志的掩码。
每个标志的解释如表4.1-A所示。
Table 4.1-A. 类访问和属性修改符
标志名称
| 值 | 解释说明 |
ACC_PUBLIC | 0x0001 | 声明为public;可以从其包外部访问。 |
ACC_FINAL | 0x0010 | 声明为final;不允许子类。 |
ACC_SUPER | 0x0020 | 特别是在被调用的特殊指令调用时,处理超类方法。 |
ACC_INTERFACE | 0x0200 | 是一个接口,而不是一个类。 |
ACC_ABSTRACT | 0x0400 | 声明为abstract;不能被实例化。 |
ACC_SYNTHETIC | 0x1000 | 声明为 synthetic合成;在源代码中不存在。 |
ACC_ANNOTATION | 0x2000 | 声明为 annotation注解类型。 |
ACC_ENUM | 0x4000 | 声明为 enum类型 |
接口通过设置ACC_INTERFACE标志来区分。
如果没有设置ACC_INTERFACE标志,则此 class文件定义了一个类,而不是一个接口。
如果设置了ACC_INTERFACE标志,则还必须设置ACC_ABSTRACT标志,并且不能设置ACC_FINAL、ACC_SUPER和ACC_ENUM标志。
如果未设置ACC_INTERFACE标志,则可以设置除ACC_ANNOTATION之外的表4.1-A可能设置除了ACC_ANNOTATION。但是,这样的类文件不能同时设置其ACC_FINAL和ACC_ABSTRACT标志(JLS 8.1.1.2)。
ACC_SUPER标志表示如果这个 invokespecial特殊指令出现在这个类或接口中,则表示两个备选语义中的哪一种。对Java虚拟机的指令集的编译器应该设置ACC_SUPER标志。在Java SE 8及更高版本中,Java虚拟机认为要在每个 class文件中都设置ACC_SUPER标志,而不管类文件中该标志的实际值和 class文件的版本如何。
ACC_SUPER标志的存在是为了向后与由Java编程语言的旧编译器编译的代码兼容。
在1.0.2之前的JDK版本中,编译器生成了access_flag,其中现在表示ACC_SUPER的标志没有指定的意义,如果Oracle的Java虚拟机实现设置了该标志,则会忽略它。
ACC_SYNTHETIC标志表示此类或接口是由编译器生成的,并且不会出现在源代码中。
注释类型必须已设置了其ACC_ANNOTATION标志。
如果设置了ACC_ANNOTATION标志,则还必须设置ACC_INTERFACE标志。
ACC_ENUM标志表示此类或其超类被声明为枚举类型。
表4.1-A中未分配的access_flags项的所有位都被保留以供将来使用。
在生成的 class文件中,它们应该被设置为零,并且应该被Java虚拟机实现所忽略。
this_class
this_class项的值必须是常量_pool表中的有效索引。
该索引处的 costant_pool条目必须是CONSTANT_Class_info结构(4.4.1),表示该 class文件定义的类或接口。
super_class
对于一个类,超级类项的值必须为零,或者必须是constant_pool表中的有效索引。
如果 super_class项的值为非零,则该索引上的 constant_pool条目必须是一个CONSTANT_Class_info结构,表示由该类文件定义的类的直接超类。
直接超类和它的任何超类都不能在其ClassFile结构的access_flags项中设置ACC_FINAL标志。
如果super_class项的值为零,则 this类文件必须表示类object,这是唯一没有直接超类的类或接口。
对于接口,super_class项的值必须始终是constant_pool表中的有效索引。
该索引处的 constant_pool条目必须是表示类 Object的CONSTANT_Class_info结构。
interfaces_count
interfaces_count项的值给出了这个类或接口类型的直接超接口的数量。
interfaces[]
interfaces数组中的每个值都必须是 constant_pool表中的有效索引。
每个 interfaces[i] 处的常数池条目,其中0≤i<interfaces_count,必须是一个CONSTANT_Class_info结构,表示该类或接口类型的直接超接口,按该类型的源代码中给出的从左到右的顺序排列。
fields_count
字段 field_count项的值给出了字段表中 field_info结构的数量。
field_info结构表示由此类或接口类型声明的所有字段,包括类变量和实例变量。
fields[]
fileds表中的每个值都必须是一个field_info结构(4.5),它给出了此类或接口中的一个字段的完整描述。
fileds表中只包含由此类或接口声明的那些字段。它不包括表示从超类或超接口继承的字段的项。
methods_count
methods_count项的值给出了 methods表中methoc_info结构的数量。
methods[]
methods表中的每个值都必须是method_info结构,(4.6)给出此类或接口中方法的完整描述。
如果在 method_info结构的 access_flags项中没有设置ACC_NATIVE和ACC_ABSTRACT标志,那么也会提供实现该方法的Java虚拟机指令。
method_info结构表示由此类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法(2.9),以及任何类或接口初始化方法(2.9)。
methods表不包括表示从超类或超接口继承的方法的项。
attributes_count
attributes_count项的值表示此类的属性表中的属性数量。
attributes[]
attributes表中的每个值都必须是一个 attributes_info结构(4.7)。
表4.7-C 中列出了由此规范定义的属性,它们出现在 ClassFile结构的属性表中。
关于要出现在 ClassFile结构的属性表中所定义的 attributes的规则在4.7中给出。
ClassFile结构的 attributes表中给出了与非预定义属性的相关的规则。
二、名称的内部形式(The Internal Form of Names)
2.1 二进制类和接口名称
出现在 class文件结构中的类名称和接口名称总是以一种完全限定的形式表示,称为二进制名称(JLS 13.1)。这些名称总是表示为CONSTANT_Utf8_info结构(4.4.7),因此可以从整个Unicode代码空间中绘制,而不受进一步的约束。类和接口名称引用自那些CONSTANT_NameAndType_info结构(4.4.6),这些结构属于描述符(4.3),以及所有CONSTANT_Class_info结构(4.4.1)。
由于历史原因,出现在类文件结构中的二进制名称的语法与JLS 13.1中记录的二进制名称的语法不同。在这种内部形式中,ASCII周期(.)通常分隔组成二进制名称的标识符会被ASCII向前斜杠(/)取代。标识符本身必须是非限定(unqualified)的名称(4.2.2)。
例如,Thread类的普通二进制名称是java.lang.Thread。
在类文件格式的描述符中使用的内部形式中,对类线程名称的引用是使用表示字符串java/lang/Thread的CONSTANT_Utf8_info结构实现的。
2.2 非限定名称
方法、字段、局部变量和形式参数的名称存储为非限定名称。非限定的名称必须包含至少一个Unicode代码点,并且不能包含任何ASCII字符 . ;[ / (即句点或分号或左方括号或正斜杠)。
方法名称被进一步约束,因此除了特殊的方法名<init>和<clinit>(2.9)外,它们不能包含ASCII字符<或>(即左角括号或右角括号)。
注意,字段名或接口方法名可以是<init>或<client>,但是没有方法调用指令可以引用<client>,只有 invokespecial 指令可以引用<init>。
三、描述符(Descriptors)
描述符是表示字段或方法的类型的字符串。描述符使用修改后的UTF-8字符串(4.4.7)以类文件格式表示,因此可以从整个Unicode代码空间中绘制,而不受进一步的约束。
3.1 语法符号(Grammar Notation)
描述符使用语法指定的。语法是一组产品,它描述了字符序列如何形成语法上正确的各种描述符。语法的终端符号以固定宽度的字体显示。非终端符号以斜体类型表示。非终端的定义由被定义的非终端的名称引入,后面是冒号。一个或多个非终端的替代定义,然后遵循后续的行。
产品右侧的语法{x}表示x。
产品右侧的短语(one of)表示以下一行或几行上的每个终端符号都是另一种定义。
3.2 字段描述符(Field Descriptors)
字段描述符表示类、实例或局部变量的类型。
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的字符,ObjectType的 L 和 ; ,以及ArrayType的 [ 都是ASCII字符。
ClassName表示以内部形式(4.2.1)编码的二进制类或接口名称。
字段描述符作为类型的解释如表4.3-A所示。
表示数组类型的字段描述符仅在表示具有255个或更少维度的类型时才有效。
Table 4.3-A. 字段描述符的解释
字段类型术语 FieldType term | 类型Type | 解释说明 |
B | byte | 带符号字节 |
C | char | 基本多语言平面中的Unicode字符代码点,用UTF-16编码 |
D | double | 双精度浮点值 |
F | float | 单精度浮点值 |
I | int | n. 整数 |
J | long | 长整数 |
L | reference | 类 ClassName的一个实例 |
S ClassName ; | short | 有符号短整型 |
Z | boolean | true 或 false |
[ | reference | 一个数组维度 |
具有 int类型的实例变量的字段描述符仅为 I
Object类型的实例变量的字段描述符是Ljava/lang/Object;
请注意,我们使用了Object类的二进制名称的内部形式。
多维数组类型 double[] [] [] 的实例变量的字段描述符为 [ [ [ D。
3.3 方法描述符(Method Descriptors)
方法描述符包含0个或多个参数描述符,表示方法所接受的参数的类型,以及一个返回描述符,表示方法返回的值的类型(如果有的话)。
MethodDescriptor:
( {ParameterDescriptor} ) ReturnDescriptor
ParameterDescriptor:
FieldType
ReturnDescriptor:
FieldType
VoidDescriptor
VoidDescriptor:
V
字符V表示该方法不返回任何值(其结果无效)
该方法的方法描述符:
Object m(int i, double d, Thread t) {...}
是
(IDLjava/lang/Thread;)Ljava/lang/Object;
请注意,我们使用了Thread和Object的二进制名称的内部形式。
方法描述符仅在表示总长度大于或等于255的方法参数时有效,其中 this长度包括在实例或接口方法调用时对该参数的贡献。总长度是通过对单个参数的贡献相加来计算的,其中一个类型为long或double的参数对长度贡献两个单位,而任何其他类型的参数贡献一个单位。
方法描述符与它所描述的方法是类方法还是实例方法都是相同的。尽管这样传递了一个实例方法,但是对正在调用 this方法的对象的引用,除了其预期的参数之外,这一事实并没有反映在方法描述符中。对 this的引用是由调用实例方法的Java虚拟机指令(2.6.1,4.11)。
四、常量池(The Constant Pool)
Java虚拟机指令不依赖于类,接口,类实例或数组的运行时布局。相反,语句引用constant_pool表中的符号信息。
所有constant_pool表项都具有以下通用格式:
cp_info {
u1 tag;
u1 info[];
}
constant_pool表中的每个项目必须以1字节标记开头,指示cp_info条目的类型。
info 数组的内容随 tag的值而变化。
表4.4-A中列出了有效的标记及其值。每个标记字节后面必须有两个或多个字节,以给出有关特定常量的信息。附加信息的格式随标签值的不同而不同。
常量类型 | 值 |
CONSTANT_Class
| 7 |
CONSTANT_Fieldref
| 9 |
CONSTANT_Methodref
| 10 |
CONSTANT_InterfaceMethodref
| 11 |
CONSTANT_String
| 8 |
CONSTANT_Integer
| 3 |
CONSTANT_Float
| 4 |
CONSTANT_Long
| 5 |
CONSTANT_Double
| 6 |
CONSTANT_NameAndType
| 12 |
CONSTANT_Utf8
| 1 |
CONSTANT_MethodHandle
| 15 |
CONSTANT_MethodType
| 16 |
CONSTANT_InvokeDynamic
| 18 |
4.1 CONSTANT_Class_info结构
CONSTANT_Class_info 结构用于表示一个类或一个接口:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
CONSTANT_Class_info结构的各项如下:
例如,表示二维数组类型int[][]的类名为[[I],而表示Thread[]类型的类名为[Ljava/lang/Thread; .
数组类型描述符只有在表示255个或更少的维度时才有效。
4.2 CONSTANT_Fieldref_info, CONSTANT_Methodref_info,和CONSTANT_InterfaceMethodref_info结构
字段、方法和接口方法都用类似的结构来表示:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
tag
CONSTANT_Fieldref_info结构的标记项的值为CONSTANT_Fieldref(9)。
CONSTANT_Methodref_info结构的标记项的值为CONSTANT_Methodref(10)。
CONSTANT_InterfaceMethodref_info结构的标记项的值为CONSTANT_InterfaceMethodref(11)。
class_index
class_index项的值必须是constant_pool表中的有效索引。该索引处的costant_pool项必须是CONSTANT_Class_info结构(4.4.1),表示具有字段或方法作为成员的类或接口类型。
CONSTANT_Methodref_info结构的class_index项必须是类类型,而不是接口类型。
CONSTANT_InterfaceMethodref_info结构的class_index项必须是接口类型。
CONSTANT_Fieldref_info结构中的class_index项可以是类类型,也可以是接口类型。
name_and_type_index
name_and_type_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool项必须是一个CONSTANT_NameAndType_info结构(4.4.6)。这个constant_pool条目指示字段或方法的名称和描述符。
在CONSTANT_Fieldref_info中,所指示的描述符必须是一个字段描述符(4.3.2)。否则,指定的描述符必须是一个方法描述符(4.3.3)。
如果CONSTANT_Methodref_info结构的方法的名称以 '<' ('\u003c') 开头,则该名称必须是特殊名称 <init>,表示一个实例初始化方法(2.9)。此种方法的返回类型必须为void。
4.3 CONSTANT_String_info结构
CONSTANT_String_info结构用于表示String类型的常量对象:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
CONSTANT_String_info结构的项如下:
tag
CONSTANT_String_info结构的标记项的值为CONSTANT_String(8)。
string_index
string_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool项必须是一个CONSTANT_Utf8_info结构(4.4.7),表示要初始化 String对象的Unicode代码点的序列。
4.4 CONSTANT_Integer_info 和 CONSTANT_Float_info 结构
CONSTANT_Integer_info和CONSTANT_Float_info结构表示4字节的数字(int和float)常量:
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
这些结构的项如下:
tag
CONSTANT_Integer_info结构的标记项的值为CONSTANT_Integer(3)。
CONSTANT_Float_info结构的标记项的值为“CONSTANT_Float(4)”。
bytes
CONSTANT_Integer_info结构的字节项表示int常量的值。值的字节以大单位(高字节优先)顺序存储。
CONSTANT_Float_info结构的字节项表示IEEE 754浮点单格式(2.3.2)中的浮点常数的值。单格式表示的字节以大端(高字节优先)顺序存储。
由CONSTANT_Float_info结构所表示的值确定如下。该值的字节首先被转换为一个int常数位。然后
- 如果位是0x7f800000,float数值将为正无穷大。
- 如果位是0xff800000,float数值将为负无穷大。
- 如果位在0x7f800001到0x7fffffff范围内,或在0xff800001到0xffffffff范围内,则float值将为NaN。
- 在所有其他情况下,让s、e和m是三个可以从位中计算出来的值:
int s = ((bits >> 31) == 0) ? 1 : -1;
int e = ((bits >> 23) & 0xff);
int m = (e == 0) ?
(bits & 0x7fffff) << 1 :
(bits & 0x7fffff) | 0x800000;
则浮点数值等于数学表达式s·m·2^(e-150)的结果。
五、字段(Fields)
六、方法(Methods)
七、属性(Attributes)
八、格式化检查(Format Checking)
九、对Java虚拟机代码的约束(Constraints on Java Virtual Machine Code)
十、class文件的验证(Verification of class Files)
十一、Java虚拟机的限制(Limitations of the Java Virtual Machine)
Java虚拟机的以下限制隐含在类文件格式中:
- 通过ClassFile结构(4.1)的16位 constant_pool_count字段,每个类或每个接口的常数池被限制为65535个条目。这是对单个类或接口的总复杂性的内部限制。
- 根据ClassFile结构(4.1)的 fields_count项的大小,类或接口可以声明的字段数量被限制为65535。
请注意,类文件结构中的 fields_count项的值不包括从超类或超接口继承的字段。 - 根据ClassFile结构的 methods_count项的大小,类或接口可以声明的方法的数量被限制为65535。
注意,类文件结构的 mettucs_count项的值不包括从超类或超接口继承的方法。 - 根据ClassFile结构的 intefaces_count项的大小,一个类或接口的直接超接口的数量被限制为65535
- 最大数量的本地变量的本地变量数组的帧创建在调用的方法(2.6)被限制在65535的大小项目max_locals的代码属性(4.7.3)给方法的代码,和16位本地变量的Java虚拟机的索引指令集。
请注意,long 和 double类型的值都被认为保留两个局部变量,并对max_locals值贡献两个单位,因此使用这些类型的局部变量进一步降低了这个限制。 - 通过Code属性(4.7.3)的max_stack字段,帧(2.6)中的操作数堆栈的大小被限制为65535个值。
请注意,长类型和双倍类型的值都被认为对max_stack值贡献了两个单位,因此在操作数堆栈上使用这些类型的值进一步降低了这个限制。 - 根据方法描述符(4.3.3)的定义,方法参数的数量被限制为255个,其中,在实例或接口方法调用时,该this限制包括一个单位。
请注意,方法描述符是根据方法参数长度的概念定义的,其中类型 long或 double的参数对长度贡献两个单位,因此这些类型的参数进一步降低了限制。 - CONSTANT_Utf8_info结构的16位无符号 length项(4.4.7)将字段和方法名、字段和方法描述符以及其他常量字符串值(包括 ConstantValue(4.7.2)属性)的长度限制为65535个字符。
- 数组中的维度数受 multianewarray指令的 dimensions操作码的大小以及 multianewarray、anewarray和 newarray指令(4.9.1,4.9.2)的限制为255。