Appearance
第4章 Class 文件格式
提示
来自deepseek解释
原文链接:https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html
本章描述了Java虚拟机的class文件格式。每个class文件包含单个类、接口或模块的定义。尽管类、接口或模块不一定需要在文件中存在字面上的外部表示(例如,因为该类是由类加载器生成的),但我们仍将通俗地将类、接口或模块的任何有效表示称为处于class文件格式中。
一个class文件由8位字节流组成。16位和32位的量分别通过读取连续的2个和4个8位字节来构造。多字节数据项始终按大端序存储,其中高位字节在前。本章定义了数据类型u1、u2和u4,分别表示无符号的1字节、2字节或4字节量。
在Java SE平台API中,class文件格式由接口java.io.DataInput和java.io.DataOutput以及类(如java.io.DataInputStream和java.io.DataOutputStream)支持。例如,类型u1、u2和u4的值可以通过接口java.io.DataInput中的readUnsignedByte、readUnsignedShort和readInt等方法读取。
本章使用类似C的结构表示法,以伪结构的形式呈现class文件格式。为避免与类及类实例的字段等混淆,描述class文件格式的结构中的内容被称为项。连续的项按顺序存储在class文件中,没有填充或对齐。
在几个class文件结构中使用了由零个或多个可变大小项组成的表。尽管我们使用类似C的数组语法来引用表项,但表是由可变大小结构组成的流这一事实意味着无法直接将表索引转换为表中的字节偏移量。
当我们引用数据结构为数组时,它由零个或多个连续的固定大小的项组成,并且可以像数组一样被索引。
本章中对ASCII字符的引用应解释为与该ASCII字符对应的Unicode码点。
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结构中的项如下:
magicmagic项提供了标识class文件格式的魔数,其值为0xCAFEBABE。
minor_version, major_versionminor_version和major_version项的值分别是此class文件的次版本号和主版本号。主版本号和次版本号共同决定了class文件格式的版本。如果一个class文件的主版本号为M,次版本号为m,我们将其class文件格式版本表示为M.m。
符合Java SE N规范的Java虚拟机实现必须完全支持表4.1-A第四列中指定的class文件格式主版本号。表示法A .. B表示从A到B(包括A和B)的主版本号。第三列“Major”显示了每个Java SE版本引入的主版本号,即第一个能够接受包含该major_version项的class文件的版本。对于非常早期的版本,显示的是JDK版本而不是Java SE版本。
表4.1-A. class文件格式主版本号
| Java SE版本 | 发布年份 | 引入的主版本 | 支持的主版本 |
|---|---|---|---|
| 1.0.2 | 1996年5月 | 45 | 45 |
| 1.1 | 1997年2月 | 45 | 45 |
| 1.2 | 1998年12月 | 46 | 45..46 |
| 1.3 | 2000年5月 | 47 | 45..47 |
| 1.4 | 2002年2月 | 48 | 45..48 |
| 5.0 | 2004年9月 | 49 | 45..49 |
| 6 | 2006年12月 | 50 | 45..50 |
| 7 | 2011年7月 | 51 | 45..51 |
| 8 | 2014年3月 | 52 | 45..52 |
| 9 | 2017年9月 | 53 | 45..53 |
| 10 | 2018年3月 | 54 | 45..54 |
| 11 | 2018年9月 | 55 | 45..55 |
| 12 | 2019年3月 | 56 | 45..56 |
| 13 | 2019年9月 | 57 | 45..57 |
| 14 | 2020年3月 | 58 | 45..58 |
| 15 | 2020年9月 | 59 | 45..59 |
| 16 | 2021年3月 | 60 | 45..60 |
| 17 | 2021年9月 | 61 | 45..61 |
对于主版本号为56或以上的class文件,次版本号必须为0或65535。
对于主版本号在45到55之间(包含端点)的class文件,次版本号可以是任何值。
关于JDK对class文件格式版本的支持,有必要回顾一下历史。JDK 1.0.2支持版本45.0到45.3(包含端点)。JDK 1.1支持版本45.0到45.65535(包含端点)。当JDK 1.2引入对主版本46的支持时,该主版本下支持的次版本仅为0。后来的JDK延续了引入新主版本(47、48等)的做法,但在新主版本下仅支持次版本0。最后,Java SE 12中预览功能的引入(见下文)促使次版本在class文件格式版本中发挥了标准作用。后续的JDK引入对N.0和N.65535的支持,其中N是所实现的Java SE平台对应的主版本号。例如,JDK 13支持57.0和57.65535。
Java SE平台可以定义预览功能。符合Java SE N(N ≥ 12)规范的Java虚拟机实现必须支持Java SE N的所有预览功能,而不支持任何其他Java SE版本的预览功能。该实现必须默认禁用所支持的预览功能,并且必须提供一种启用所有这些功能的方法,且不得提供仅启用其中一部分功能的方法。
如果一个class文件的主版本号对应于Java SE N(根据表4.1-A),且次版本号为65535,则称该class文件依赖于Java SE N(N ≥ 12)的预览功能。
符合Java SE N(N ≥ 12)规范的Java虚拟机实现必须按如下方式运行:
- 仅当Java SE
N的预览功能被启用时,才能加载依赖于Java SEN预览功能的class文件。 - 依赖于其他Java SE版本预览功能的
class文件绝不能被加载。 - 不依赖于任何Java SE版本预览功能的
class文件,无论Java SEN的预览功能是否被启用,都可以被加载。
constant_pool_countconstant_pool_count项的值等于constant_pool表中的条目数加一。如果常量池索引大于零且小于constant_pool_count,则该索引被认为是有效的,但§4.4.5中提到的long和double类型的常量除外。
constant_pool[]constant_pool是一个由结构(§4.4)组成的表,表示各种字符串常量、类和接口名称、字段名称以及在ClassFile结构及其子结构中引用的其他常量。每个constant_pool表项的格式由其第一个“标签”字节指示。
constant_pool表从1索引到constant_pool_count - 1。
access_flagsaccess_flags项的值是一个标志掩码,用于表示该类或接口的访问权限和属性。每个标志(当被设置时)的解释在表4.1-B中指定。
表4.1-B. 类访问和属性修饰符
| 标志名称 | 值 | 解释 |
|---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public;可从其包外部访问。 |
ACC_FINAL | 0x0010 | 声明为final;不允许有子类。 |
ACC_SUPER | 0x0020 | 当由invokespecial指令调用时,特殊处理超类方法。 |
ACC_INTERFACE | 0x0200 | 是接口,不是类。 |
ACC_ABSTRACT | 0x0400 | 声明为abstract;不能被实例化。 |
ACC_SYNTHETIC | 0x1000 | 声明为synthetic;不存在于源代码中。 |
ACC_ANNOTATION | 0x2000 | 声明为注解接口。 |
ACC_ENUM | 0x4000 | 声明为枚举类。 |
ACC_MODULE | 0x8000 | 是模块,不是类或接口。 |
ACC_MODULE标志指示此class文件定义了一个模块,而不是类或接口。如果设置了ACC_MODULE标志,则适用于该class文件的特殊规则在本节末尾给出。如果未设置ACC_MODULE标志,则当前段落下方的规则适用于该class文件。
接口通过设置ACC_INTERFACE标志来区分。如果未设置ACC_INTERFACE标志,则此class文件定义了一个类,而不是接口或模块。
如果设置了ACC_INTERFACE标志,则还必须设置ACC_ABSTRACT标志,并且不得设置ACC_FINAL、ACC_SUPER、ACC_ENUM和ACC_MODULE标志。
如果未设置ACC_INTERFACE标志,则可以设置表4.1-B中的任何其他标志,但ACC_ANNOTATION和ACC_MODULE除外。然而,这样的class文件不得同时设置其ACC_FINAL和ACC_ABSTRACT标志(JLS §8.1.1.2)。
ACC_SUPER标志指示如果此类或接口中出现invokespecial指令(§invokespecial),则该指令应表达两种替代语义中的哪一种。面向Java虚拟机指令集的编译器应设置ACC_SUPER标志。在Java SE 8及更高版本中,Java虚拟机认为ACC_SUPER标志在每个class文件中都被设置,无论该标志在class文件中的实际值和class文件的版本如何。
ACC_SUPER标志的存在是为了与旧版Java编程语言编译器编译的代码向后兼容。在JDK 1.0.2之前,编译器生成的access_flags中,现在代表ACC_SUPER的标志没有分配含义,Oracle的Java虚拟机实现在该标志被设置时忽略它。
ACC_SYNTHETIC标志指示此类或接口是由编译器生成的,且不存在于源代码中。
注解接口(JLS §9.6)必须设置其ACC_ANNOTATION标志。如果设置了ACC_ANNOTATION标志,则还必须设置ACC_INTERFACE标志。
ACC_ENUM标志指示此类或其超类被声明为枚举类(JLS §8.9)。
表4.1-B中未分配的所有access_flags项中的位都保留供将来使用。它们在生成的class文件中应设置为零,Java虚拟机实现应忽略它们。
this_classthis_class项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Class_info结构(§4.4.1),表示由此class文件定义的类或接口。
super_class 对于类,super_class项的值要么必须为零,要么必须是constant_pool表中的有效索引。如果super_class项的值非零,则该索引处的constant_pool条目必须是一个CONSTANT_Class_info结构,表示由此class文件定义的类的直接超类。直接超类及其任何超类都不得在其ClassFile结构的access_flags项中设置ACC_FINAL标志。
如果super_class项的值为零,则此class文件必须表示Object类,这是唯一没有直接超类的类或接口。
对于接口,super_class项的值必须始终是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个表示Object类的CONSTANT_Class_info结构。
interfaces_countinterfaces_count项的值给出了此类或接口类型的直接超接口的数量。
interfaces[]interfaces数组中的每个值必须是constant_pool表中的有效索引。interfaces[i](其中0 ≤ i < interfaces_count)的每个值处的constant_pool条目必须是一个CONSTANT_Class_info结构,表示一个接口,该接口是该类或接口类型的直接超接口,顺序按源代码中为类型给定的从左到右的顺序。
fields_countfields_count项的值给出了fields表中field_info结构的数量。field_info结构表示此类或接口类型声明的所有字段,包括类变量和实例变量。
fields[]fields表中的每个值必须是一个field_info结构(§4.5),完整描述此类或接口中的字段。fields表仅包括此类或接口声明的字段。它不包括表示从超类或超接口继承的字段的项。
methods_countmethods_count项的值给出了methods表中method_info结构的数量。
methods[]methods表中的每个值必须是一个method_info结构(§4.6),完整描述此类或接口中的方法。如果method_info结构的access_flags项中既未设置ACC_NATIVE标志也未设置ACC_ABSTRACT标志,则还会提供实现该方法的Java虚拟机指令。
method_info结构表示此类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法(§2.9.1)以及任何类或接口初始化方法(§2.9.2)。methods表不包括表示从超类或超接口继承的方法的项。
attributes_countattributes_count项的值给出了此类的attributes表中的属性数量。
attributes[]attributes表中的每个值必须是一个attribute_info结构(§4.7)。
本规范定义为出现在ClassFile结构的attributes表中的属性列于表4.7-C中。
关于定义出现在ClassFile结构的attributes表中的属性的规则在§4.7中给出。
关于ClassFile结构的attributes表中非预定义属性的规则在§4.7.1中给出。
如果在access_flags项中设置了ACC_MODULE标志,则access_flags项中不得设置任何其他标志,并且以下规则适用于ClassFile结构的其余部分:
major_version,minor_version:≥ 53.0(即Java SE 9及以上)this_class:module-infosuper_class,interfaces_count,fields_count,methods_count:零attributes:必须存在一个Module属性。除Module、ModulePackages、ModuleMainClass、InnerClasses、SourceFile、SourceDebugExtension、RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations外,预定义属性(§4.7)均不得出现。
4.2 名称
4.2.1 二进制类名与接口名
出现在class文件结构中的类和接口名称始终以完全限定形式表示,即所谓的二进制名称(JLS §13.1)。此类名称始终表示为CONSTANT_Utf8_info结构(§4.4.7),因此,在没有进一步约束的情况下,可以从整个Unicode代码空间中选取。类和接口名称从那些在其描述符(§4.3)中具有此类名称的CONSTANT_NameAndType_info结构(§4.4.6)以及所有CONSTANT_Class_info结构(§4.4.1)中被引用。
由于历史原因,class文件结构中出现的二进制名称的语法与JLS §13.1中记录的二进制名称的语法不同。在这种内部形式中,通常分隔组成二进制名称的标识符的ASCII句点(.)被ASCII正斜杠(/)替换。标识符本身必须是非限定名称(§4.2.2)。
例如,Thread类的普通二进制名称是java.lang.Thread。在class文件格式的描述符中使用的内部形式中,对Thread类名称的引用是使用表示字符串java/lang/Thread的CONSTANT_Utf8_info结构来实现的。
4.2.2 非限定名称
方法、字段、局部变量和形参的名称存储为非限定名称。非限定名称必须至少包含一个Unicode码点,并且不得包含任何ASCII字符 . ; [ /(即句点、分号、左方括号或正斜杠)。
方法名称受到进一步约束,除了特殊方法名称<init>和<clinit>(§2.9)之外,它们不得包含ASCII字符 < 或 >(即左尖括号或右尖括号)。
注意,字段名称或接口方法名称可以是<init>或<clinit>,但没有方法调用指令可以引用<clinit>,且只有invokespecial指令(§invokespecial)可以引用<init>。
4.2.3 模块名称与包名称
从Module属性引用的模块名称存储在常量池(§4.4.11)的CONSTANT_Module_info结构中。CONSTANT_Module_info结构包装了一个表示模块名称的CONSTANT_Utf8_info结构。模块名称不像类和接口名称那样以“内部形式”编码,即分隔模块名称中标识符的ASCII句点(.)不会被ASCII正斜杠(/)替换。
模块名称可以从整个Unicode代码空间中选取,但须遵守以下约束:
- 模块名称不得包含范围
'\u0000'到'\u001F'(含)内的任何码点。 - ASCII反斜杠(
\)保留用作模块名称中的转义字符。它不得出现在模块名称中,除非后面紧跟ASCII反斜杠、ASCII冒号(:)或ASCII at符号(@)。ASCII字符序列\\可用于在模块名称中编码反斜杠。 - ASCII冒号(
:)和at符号(@)保留供将来在模块名称中使用。除非被转义,否则它们不得出现在模块名称中。ASCII字符序列\:和\@可用于在模块名称中编码冒号和at符号。
从Module属性引用的包名称存储在常量池(§4.4.12)的CONSTANT_Package_info结构中。CONSTANT_Package_info结构包装了一个CONSTANT_Utf8_info结构,该结构表示以内部形式编码的包名称。
4.3 描述符
描述符是表示字段或方法类型的字符串。描述符在class文件格式中使用修改过的UTF-8字符串(§4.4.7)表示,因此,在没有进一步约束的情况下,可以从整个Unicode代码空间中选取。
4.3.1 语法符号
描述符使用文法来指定。该文法是一组产生式,描述字符序列如何能够形成各种类型的语法正确的描述符。文法的终结符号以等宽字体显示。非终结符号以斜体显示。非终结符的定义由被定义的非终结符名称后跟冒号引入。随后在后续行中给出该非终结符的一个或多个替代定义。
产生式右侧的语法 {x} 表示零个或多个 x 的出现。
产生式右侧的短语 (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:
FieldTypeBaseType的字符、ObjectType的L和;,以及ArrayType的[都是ASCII字符。
ClassName表示以内部形式(§4.2.1)编码的二进制类或接口名称。
字段描述符作为类型的解释如表4.3-A所示。
表示数组类型的字段描述符仅当它表示的类型的维度不超过255时才有效。
表4.3-A. 字段描述符的解释
| 字段类型术语 | 类型 | 解释 |
|---|---|---|
B | byte | 有符号字节 |
C | char | Unicode字符码点(基本多语言平面),使用UTF-16编码 |
D | double | 双精度浮点值 |
F | float | 单精度浮点值 |
I | int | 整数 |
J | long | 长整数 |
L ClassName ; | reference | 类ClassName的实例 |
S | short | 有符号短整数 |
Z | boolean | true或false |
[ | reference | 一个数组维度 |
int类型实例变量的字段描述符就是简单的I。
Object类型实例变量的字段描述符是Ljava/lang/Object;。注意,类Object的二进制名称的内部形式被使用。
多维数组类型double[][][]的实例变量的字段描述符是[[[D。
4.3.3 方法描述符
方法描述符包含零个或多个参数描述符(表示方法所取参数的类型)和一个返回描述符(表示方法返回的值(如果有)的类型)。
MethodDescriptor:
{ ParameterDescriptor } ReturnDescriptor
ParameterDescriptor:
FieldType
ReturnDescriptor:
FieldType
VoidDescriptor
VoidDescriptor:
V字符V表示方法不返回值(其结果类型为void)。
方法的方法描述符:
java
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)。
4.4 常量池
Java虚拟机指令不依赖于类、接口、类实例或数组的运行时布局。相反,指令引用constant_pool表中的符号信息。
所有constant_pool表项都具有以下通用格式:
cp_info {
u1 tag;
u1 info[];
}constant_pool表中的每个条目必须以一个1字节的标签开头,指示该条目所表示的常量种类。共有17种常量,列于表4.4-A中,并附有相应的标签,并按本章节编号排序。每个标签字节后必须跟有两个或多个字节,给出关于特定常量的信息。附加信息的格式取决于标签字节,即info数组的内容随tag的值而变化。
表4.4-A. 常量池标签(按节)
| 常量种类 | 标签 | 节 |
|---|---|---|
CONSTANT_Class | 7 | §4.4.1 |
CONSTANT_Fieldref | 9 | §4.4.2 |
CONSTANT_Methodref | 10 | §4.4.2 |
CONSTANT_InterfaceMethodref | 11 | §4.4.2 |
CONSTANT_String | 8 | §4.4.3 |
CONSTANT_Integer | 3 | §4.4.4 |
CONSTANT_Float | 4 | §4.4.4 |
CONSTANT_Long | 5 | §4.4.5 |
CONSTANT_Double | 6 | §4.4.5 |
CONSTANT_NameAndType | 12 | §4.4.6 |
CONSTANT_Utf8 | 1 | §4.4.7 |
CONSTANT_MethodHandle | 15 | §4.4.8 |
CONSTANT_MethodType | 16 | §4.4.9 |
CONSTANT_Dynamic | 17 | §4.4.10 |
CONSTANT_InvokeDynamic | 18 | §4.4.10 |
CONSTANT_Module | 19 | §4.4.11 |
CONSTANT_Package | 20 | §4.4.12 |
在版本号为v的class文件中,constant_pool表中的每个条目必须具有在class文件格式版本v或更早版本中首次定义的标签(§4.1)。也就是说,每个条目必须表示一种被批准在该class文件中使用的常量。表4.4-B列出了每个标签及其首次定义的class文件格式版本。同时还显示了引入该class文件格式版本的Java SE平台版本。
表4.4-B. 常量池标签(按标签)
| 常量种类 | 标签 | class文件格式版本 | Java SE版本 |
|---|---|---|---|
CONSTANT_Utf8 | 1 | 45.3 | 1.0.2 |
CONSTANT_Integer | 3 | 45.3 | 1.0.2 |
CONSTANT_Float | 4 | 45.3 | 1.0.2 |
CONSTANT_Long | 5 | 45.3 | 1.0.2 |
CONSTANT_Double | 6 | 45.3 | 1.0.2 |
CONSTANT_Class | 7 | 45.3 | 1.0.2 |
CONSTANT_String | 8 | 45.3 | 1.0.2 |
CONSTANT_Fieldref | 9 | 45.3 | 1.0.2 |
CONSTANT_Methodref | 10 | 45.3 | 1.0.2 |
CONSTANT_InterfaceMethodref | 11 | 45.3 | 1.0.2 |
CONSTANT_NameAndType | 12 | 45.3 | 1.0.2 |
CONSTANT_MethodHandle | 15 | 51.0 | 7 |
CONSTANT_MethodType | 16 | 51.0 | 7 |
CONSTANT_Dynamic | 17 | 55.0 | 11 |
CONSTANT_InvokeDynamic | 18 | 51.0 | 7 |
CONSTANT_Module | 19 | 53.0 | 9 |
CONSTANT_Package | 20 | 53.0 | 9 |
常量池中的某些条目是可加载的,因为它们表示可以在运行时压入栈以支持进一步计算的实体。在版本号为v的class文件中,如果constant_pool表中的条目具有在class文件格式版本v或更早版本中首次被视为可加载的标签,则该条目是可加载的。表4.4-C列出了每个标签及其首次被视为可加载的class文件格式版本。同时还显示了引入该class文件格式版本的Java SE平台版本。
在每种情况下,除了CONSTANT_Class之外,一个标签首次被视为可加载的版本与其首次定义的class文件格式版本相同。
表4.4-C. 可加载常量池标签
| 常量种类 | 标签 | class文件格式版本 | Java SE版本 |
|---|---|---|---|
CONSTANT_Integer | 3 | 45.3 | 1.0.2 |
CONSTANT_Float | 4 | 45.3 | 1.0.2 |
CONSTANT_Long | 5 | 45.3 | 1.0.2 |
CONSTANT_Double | 6 | 45.3 | 1.0.2 |
CONSTANT_Class | 7 | 49.0 | 5.0 |
CONSTANT_String | 8 | 45.3 | 1.0.2 |
CONSTANT_MethodHandle | 15 | 51.0 | 7 |
CONSTANT_MethodType | 16 | 51.0 | 7 |
CONSTANT_Dynamic | 17 | 55.0 | 11 |
4.4.1 CONSTANT_Class_info结构
CONSTANT_Class_info结构用于表示一个类或接口:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}CONSTANT_Class_info结构的项如下:
tagtag项的值为CONSTANT_Class(7)。
name_indexname_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示以内部形式(§4.2.1)编码的有效二进制类或接口名称。
因为数组是对象,所以操作码anewarray和multianewarray——但不是操作码new——可以通过constant_pool表中的CONSTANT_Class_info结构引用数组“类”。对于此类数组类,类的名称是数组类型的描述符(§4.3.2)。
例如,表示二维数组类型int[][]的类名称是[[I,而表示类型Thread[]的类名称是[Ljava/lang/Thread;。
4.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;
}这些结构的项如下:
tagCONSTANT_Fieldref_info结构的tag项值为CONSTANT_Fieldref(9)。 CONSTANT_Methodref_info结构的tag项值为CONSTANT_Methodref(10)。 CONSTANT_InterfaceMethodref_info结构的tag项值为CONSTANT_InterfaceMethodref(11)。
class_indexclass_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Class_info结构(§4.4.1),表示具有该字段或方法作为成员的类或接口类型。
在CONSTANT_Fieldref_info结构中,class_index项可以是类类型或接口类型。
在CONSTANT_Methodref_info结构中,class_index项必须是类类型,不能是接口类型。
在CONSTANT_InterfaceMethodref_info结构中,class_index项必须是接口类型,不能是类类型。
name_and_type_indexname_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.1)。此类方法的返回类型必须为void。
4.4.3 CONSTANT_String_info结构
CONSTANT_String_info结构用于表示String类型的常量对象:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}CONSTANT_String_info结构的项如下:
tagtag项的值为CONSTANT_String(8)。
string_indexstring_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示要初始化的String对象的Unicode码点序列。
4.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;
}这些结构的项如下:
tagCONSTANT_Integer_info结构的tag项值为CONSTANT_Integer(3)。 CONSTANT_Float_info结构的tag项值为CONSTANT_Float(4)。
bytesCONSTANT_Integer_info结构的bytes项表示int常量的值。bytes项按大端序(高位字节在前)存储。
CONSTANT_Float_info结构的bytes项表示IEEE 754 binary32格式的float值(§2.3.2)。bytes项按大端序(高位字节在前)存储。其值通过将bytes转换为int常量bits来确定,然后:
- 如果
bits为0x7f800000,则float值为正无穷大。 - 如果
bits为0xff800000,则float值为负无穷大。 - 如果
bits在0x7f800001到0x7fffffff或0xff800001到0xffffffff范围内,则float值为NaN。 - 否则,令
s、e和m为从bits计算出的三个值:则浮点值等于数学表达式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)的float值。
4.4.5 CONSTANT_Long_info和CONSTANT_Double_info结构
CONSTANT_Long_info和CONSTANT_Double_info结构表示8字节数值(long和double)常量:
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}所有8字节常量在class文件的constant_pool表中占用两个条目。如果CONSTANT_Long_info或CONSTANT_Double_info结构是constant_pool表中索引n处的条目,则表中下一个可用的条目位于索引n+2。常量池索引n+1必须有效但被视为不可用。
事后看来,让8字节常量占用两个常量池条目是一个糟糕的选择。
这些结构的项如下:
tagCONSTANT_Long_info结构的tag项值为CONSTANT_Long(5)。 CONSTANT_Double_info结构的tag项值为CONSTANT_Double(6)。
high_bytes, low_bytesCONSTANT_Long_info结构的无符号high_bytes和low_bytes项共同表示long常量的值:
((long) high_bytes << 32) + low_bytes其中high_bytes和low_bytes的每个字节按大端序(高位字节在前)存储。
CONSTANT_Double_info结构的high_bytes和low_bytes项共同表示IEEE 754 binary64格式的double值(§2.3.2)。每个项的字节按大端序(高位字节在前)存储。
该结构所表示的值确定如下:将high_bytes和low_bytes转换为long常量bits,等于:
((long) high_bytes << 32) + low_bytes然后:
- 如果
bits为0x7ff0000000000000L,则double值为正无穷大。 - 如果
bits为0xfff0000000000000L,则double值为负无穷大。 - 如果
bits在0x7ff0000000000001L到0x7fffffffffffffffL或0xfff0000000000001L到0xffffffffffffffffL范围内,则double值为NaN。 - 否则,令
s、e和m为从bits计算出的三个值:则浮点值等于数学表达式int s = ((bits >> 63) == 0) ? 1 : -1; int e = (int)((bits >> 52) & 0x7ffL); long m = (e == 0) ? (bits & 0xfffffffffffffL) << 1 : (bits & 0xfffffffffffffL) | 0x10000000000000L;s · m · 2^(e-1075)的double值。
4.4.6 CONSTANT_NameAndType_info结构
CONSTANT_NameAndType_info结构用于表示字段或方法,而不指明它属于哪个类或接口类型:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}CONSTANT_NameAndType_info结构的项如下:
tagtag项的值为CONSTANT_NameAndType(12)。
name_indexname_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示表示字段或方法的有效非限定名称(§4.2.2),或特殊方法名称<init>(§2.9.1)。
descriptor_indexdescriptor_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示有效的字段描述符或方法描述符(§4.3.2,§4.3.3)。
4.4.7 CONSTANT_Utf8_info结构
CONSTANT_Utf8_info结构用于表示常量字符串值:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}CONSTANT_Utf8_info结构的项如下:
tagtag项的值为CONSTANT_Utf8(1)。
lengthlength项的值给出bytes数组中的字节数(不是结果字符串的长度)。
bytes[]bytes数组包含字符串的字节。
任何字节都不得有值(byte)0。
任何字节都不得在范围(byte)0xf0到(byte)0xff内。
字符串内容以修改过的UTF-8编码。修改过的UTF-8字符串的编码方式使得仅包含非空ASCII字符的码点序列可以用每个码点1个字节表示,但Unicode代码空间中的所有码点都可以表示。修改过的UTF-8字符串不以空字符终止。编码如下:
范围
'\u0001'到'\u007F'内的码点由单个字节表示:0 bits 6-0字节中的7位数据给出所表示码点的值。
空码点(
'\u0000')和范围'\u0080'到'\u07FF'内的码点由一对字节x和y表示:x: 1 1 0 bits 10-6 y: 1 0 bits 5-0这两个字节表示值为
((x & 0x1f) << 6) + (y & 0x3f)的码点。范围
'\u0800'到'\uFFFF'内的码点由3个字节x、y和z表示:x: 1 1 1 0 bits 15-12 y: 1 0 bits 11-6 z: 1 0 bits 5-0这三个字节表示值为
((x & 0x0f) << 12) + ((y & 0x3f) << 6) + (z & 0x3f)的码点。码点高于U+FFFF(所谓的增补字符)通过分别编码其UTF-16表示的两个代理码元来表示。每个代理码元由3个字节表示。这意味着增补字符由6个字节
u、v、w、x、y和z表示:u: 1 1 1 0 1 1 0 1 v: 1 0 (bits 20-16)-1 w: 1 0 bits 15-10 x: 1 1 1 0 1 1 0 1 y: 1 0 1 1 bits 9-6 z: 1 0 bits 5-0这六个字节表示值为
0x10000 + ((v & 0x0f) << 16) + ((w & 0x3f) << 10) + ((y & 0x0f) << 6) + (z & 0x3f)的码点。
多字节字符的字节按大端序(高位字节在前)存储在class文件中。
此格式与“标准”UTF-8格式有两个区别。首先,空字符(char)0使用2字节格式而不是1字节格式编码,因此修改过的UTF-8字符串永远不会包含嵌入的空值。其次,仅使用标准UTF-8的1字节、2字节和3字节格式。Java虚拟机不识别标准UTF-8的4字节格式;它使用自己的两次三字节格式代替。
有关标准UTF-8格式的更多信息,请参见《Unicode标准13.0版》第3.9节“Unicode编码形式”。
4.4.8 CONSTANT_MethodHandle_info结构
CONSTANT_MethodHandle_info结构用于表示方法句柄:
CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}CONSTANT_MethodHandle_info结构的项如下:
tagtag项的值为CONSTANT_MethodHandle(15)。
reference_kindreference_kind项的值必须在1到9的范围内。该值表示此方法句柄的种类,它表征其字节码行为(§5.4.3.5)。
reference_indexreference_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须如下:
- 如果
reference_kind项的值是1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),则该索引处的constant_pool条目必须是一个CONSTANT_Fieldref_info结构(§4.4.2),表示要为其创建方法句柄的字段。 - 如果
reference_kind项的值是6(REF_invokeStatic)或7(REF_invokeSpecial),则如果class文件版本号小于52.0,该索引处的constant_pool条目必须是一个CONSTANT_Methodref_info结构,表示要为其创建方法句柄的类的方法;如果class文件版本号为52.0或以上,则该条目必须是CONSTANT_Methodref_info结构或CONSTANT_InterfaceMethodref_info结构(§4.4.2),表示要为其创建方法句柄的类或接口的方法。 - 如果
reference_kind项的值是9(REF_invokeInterface),则该索引处的constant_pool条目必须是一个CONSTANT_InterfaceMethodref_info结构,表示要为其创建方法句柄的接口的方法。 - 如果
reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),则由CONSTANT_Methodref_info结构或CONSTANT_InterfaceMethodref_info结构表示的方法名称不得为<init>或<clinit>。如果值为8(REF_newInvokeSpecial),则由CONSTANT_Methodref_info结构表示的方法名称必须为<init>。
4.4.9 CONSTANT_MethodType_info结构
CONSTANT_MethodType_info结构用于表示方法类型:
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}CONSTANT_MethodType_info结构的项如下:
tagtag项的值为CONSTANT_MethodType(16)。
descriptor_indexdescriptor_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示一个方法描述符(§4.3.3)。
4.4.10 CONSTANT_Dynamic_info和CONSTANT_InvokeDynamic_info结构
constant_pool表中的大多数结构通过组合静态记录在表中的名称、描述符和值来直接表示实体。相反,CONSTANT_Dynamic_info和CONSTANT_InvokeDynamic_info结构通过指向动态计算实体的代码来间接表示实体。该代码称为引导方法,由Java虚拟机在解析派生自这些结构的符号引用期间调用(§5.1,§5.4.3.6)。每个结构指定一个引导方法以及一个辅助名称和类型,用于表征要计算的实体。更详细地说:
CONSTANT_Dynamic_info结构用于表示动态计算常量,这是一个任意值,由引导方法在ldc指令(§ldc)等过程中调用产生。该结构指定的辅助类型约束动态计算常量的类型。
CONSTANT_InvokeDynamic_info结构用于表示动态计算调用点,即java.lang.invoke.CallSite的实例,由引导方法在invokedynamic指令(§invokedynamic)过程中调用产生。该结构指定的辅助类型约束动态计算调用点的方法类型。
CONSTANT_Dynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}这些结构的项如下:
tagCONSTANT_Dynamic_info结构的tag项值为CONSTANT_Dynamic(17)。 CONSTANT_InvokeDynamic_info结构的tag项值为CONSTANT_InvokeDynamic(18)。
bootstrap_method_attr_indexbootstrap_method_attr_index项的值必须是此class文件的引导方法表(§4.7.23)的bootstrap_methods数组中的有效索引。
CONSTANT_Dynamic_info结构是独特的,因为它们在语法上允许通过引导方法表引用自身。我们允许循环,但要求在解析时失败(§5.4.3.6),而不是强制在加载类时检测此类循环(这可能是一项昂贵的检查)。
name_and_type_indexname_and_type_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_NameAndType_info结构(§4.4.6)。此constant_pool条目指示一个名称和描述符。
在CONSTANT_Dynamic_info结构中,所指示的描述符必须是字段描述符(§4.3.2)。
在CONSTANT_InvokeDynamic_info结构中,所指示的描述符必须是方法描述符(§4.3.3)。
4.4.11 CONSTANT_Module_info结构
CONSTANT_Module_info结构用于表示一个模块:
CONSTANT_Module_info {
u1 tag;
u2 name_index;
}CONSTANT_Module_info结构的项如下:
tagtag项的值为CONSTANT_Module(19)。
name_indexname_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示一个有效的模块名称(§4.2.3)。
CONSTANT_Module_info结构仅允许在声明模块的class文件的常量池中出现,即access_flags项设置了ACC_MODULE标志的ClassFile结构。在所有其他class文件中,CONSTANT_Module_info结构是非法的。
4.4.12 CONSTANT_Package_info结构
CONSTANT_Package_info结构用于表示模块导出或开放的包:
CONSTANT_Package_info {
u1 tag;
u2 name_index;
}CONSTANT_Package_info结构的项如下:
tagtag项的值为CONSTANT_Package(20)。
name_indexname_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示以内部形式(§4.2.3)编码的有效包名称。
CONSTANT_Package_info结构仅允许在声明模块的class文件的常量池中出现,即access_flags项设置了ACC_MODULE标志的ClassFile结构。在所有其他class文件中,CONSTANT_Package_info结构是非法的。
4.5 字段
每个字段由一个field_info结构描述。
一个class文件中不能有两个字段具有相同的名称和描述符(§4.3.2)。
该结构具有以下格式:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}field_info结构的项如下:
access_flagsaccess_flags项的值是一个标志掩码,用于表示该字段的访问权限和属性。每个标志(当被设置时)的解释在表4.5-A中指定。
表4.5-A. 字段访问和属性标志
| 标志名称 | 值 | 解释 |
|---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public;可从其包外部访问。 |
ACC_PRIVATE | 0x0002 | 声明为private;仅在定义类及同一nest(§5.4.4)内的其他类中可访问。 |
ACC_PROTECTED | 0x0004 | 声明为protected;可在子类中访问。 |
ACC_STATIC | 0x0008 | 声明为static。 |
ACC_FINAL | 0x0010 | 声明为final;对象构造后永远不会被直接赋值(JLS §17.5)。 |
ACC_VOLATILE | 0x0040 | 声明为volatile;不能被缓存。 |
ACC_TRANSIENT | 0x0080 | 声明为transient;不被持久对象管理器写入或读取。 |
ACC_SYNTHETIC | 0x1000 | 声明为synthetic;不存在于源代码中。 |
ACC_ENUM | 0x4000 | 声明为枚举类的一个元素。 |
类的字段可以设置表4.5-A中的任何标志。但是,类的每个字段最多只能设置ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED标志中的一个(JLS §8.3.1),并且不得同时设置ACC_FINAL和ACC_VOLATILE标志(JLS §8.3.1.4)。
接口的字段必须设置其ACC_PUBLIC、ACC_STATIC和ACC_FINAL标志;它们可以设置其ACC_SYNTHETIC标志,但不得设置表4.5-A中的任何其他标志(JLS §9.3)。
ACC_SYNTHETIC标志指示此字段是由编译器生成的,不存在于源代码中。
ACC_ENUM标志指示此字段用于保存枚举类的一个元素(JLS §8.9)。
表4.5-A中未分配的所有access_flags项中的位都保留供将来使用。它们在生成的class文件中应设置为零,Java虚拟机实现应忽略它们。
name_indexname_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示表示字段的有效非限定名称(§4.2.2)。
descriptor_indexdescriptor_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示有效的字段描述符(§4.3.2)。
attributes_countattributes_count项的值指示此字段的附加属性数量。
attributes[]attributes表中的每个值必须是一个attribute_info结构(§4.7)。
一个字段可以具有任意数量的与其关联的可选属性。
本规范定义为出现在field_info结构的attributes表中的属性列于表4.7-C中。
关于定义出现在field_info结构的attributes表中的属性的规则在§4.7中给出。
关于field_info结构的attributes表中非预定义属性的规则在§4.7.1中给出。
4.6 方法
每个方法,包括每个实例初始化方法(§2.9.1)和类或接口初始化方法(§2.9.2),都由一个method_info结构描述。
一个class文件中不能有两个方法具有相同的名称和描述符(§4.3.3)。
该结构具有以下格式:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}method_info结构的项如下:
access_flagsaccess_flags项的值是一个标志掩码,用于表示该方法的访问权限和属性。每个标志(当被设置时)的解释在表4.6-A中指定。
表4.6-A. 方法访问和属性标志
| 标志名称 | 值 | 解释 |
|---|---|---|
ACC_PUBLIC | 0x0001 | 声明为public;可从其包外部访问。 |
ACC_PRIVATE | 0x0002 | 声明为private;仅在定义类及同一nest(§5.4.4)内的其他类中可访问。 |
ACC_PROTECTED | 0x0004 | 声明为protected;可在子类中访问。 |
ACC_STATIC | 0x0008 | 声明为static。 |
ACC_FINAL | 0x0010 | 声明为final;不得被覆盖(§5.4.5)。 |
ACC_SYNCHRONIZED | 0x0020 | 声明为synchronized;调用会被监视器使用包裹。 |
ACC_BRIDGE | 0x0040 | 桥接方法,由编译器生成。 |
ACC_VARARGS | 0x0080 | 声明为具有可变数量的参数。 |
ACC_NATIVE | 0x0100 | 声明为native;使用Java编程语言以外的语言实现。 |
ACC_ABSTRACT | 0x0400 | 声明为abstract;未提供实现。 |
ACC_STRICT | 0x0800 | 在主版本号至少为46且至多为60的类文件中:声明为strictfp。 |
ACC_SYNTHETIC | 0x1000 | 声明为synthetic;不存在于源代码中。 |
值0x0800仅在主版本号至少为46且至多为60的类文件中被解释为ACC_STRICT标志。对于此类文件中的方法,以下规则确定ACC_STRICT是否可以与其他标志组合设置。(在Java SE 1.2到16中,设置ACC_STRICT标志约束了方法的浮点指令(§2.8)。)对于主版本号小于46或大于60的类文件中的方法,值0x0800不被解释为ACC_STRICT标志,而是未分配的;在此类文件中“设置ACC_STRICT标志”没有意义。
类的方法可以设置表4.6-A中的任何标志。但是,类的每个方法最多只能设置ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED标志中的一个(JLS §8.4.3)。
接口的方法可以设置表4.6-A中的任何标志,但ACC_PROTECTED、ACC_FINAL、ACC_SYNCHRONIZED和ACC_NATIVE除外(JLS §9.4)。在版本号小于52.0的类文件中,接口的每个方法必须设置其ACC_PUBLIC和ACC_ABSTRACT标志;在版本号为52.0或以上的类文件中,接口的每个方法必须恰好设置ACC_PUBLIC和ACC_PRIVATE标志中的一个。
如果类或接口的方法设置了其ACC_ABSTRACT标志,则它不得设置其ACC_PRIVATE、ACC_STATIC、ACC_FINAL、ACC_SYNCHRONIZED或ACC_NATIVE标志,也不得(在主版本号至少为46且至多为60的类文件中)设置其ACC_STRICT标志。
实例初始化方法(§2.9.1)最多可以设置ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED标志中的一个,也可以设置其ACC_VARARGS和ACC_SYNTHETIC标志,还可以(在主版本号至少为46且至多为60的类文件中)设置其ACC_STRICT标志,但不得设置表4.6-A中的任何其他标志。
在版本号为51.0或以上的类文件中,名称为<clinit>的方法必须设置其ACC_STATIC标志。
类或接口初始化方法(§2.9.2)由Java虚拟机隐式调用。其access_flags项的值除了ACC_STATIC标志和(在主版本号至少为46且至多为60的类文件中)ACC_STRICT标志的设置外,均被忽略,并且该方法免于上述关于合法标志组合的规则。
ACC_BRIDGE标志用于指示由Java编程语言编译器生成的桥接方法。
ACC_VARARGS标志指示此方法在源代码级别接受可变数量的参数。声明为接受可变数量参数的方法必须将其ACC_VARARGS标志设置为1。所有其他方法必须将其ACC_VARARGS标志设置为0。
ACC_SYNTHETIC标志指示此方法是由编译器生成的,不存在于源代码中,除非它是§4.7.8中命名的方法之一。
表4.6-A中未分配的所有access_flags项中的位都保留供将来使用。(这包括在主版本号小于46或大于60的类文件中对应于0x0800的位。)它们在生成的class文件中应设置为零,Java虚拟机实现应忽略它们。
name_indexname_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示表示方法的有效非限定名称(§4.2.2),或(如果此方法在类中而不是接口中)特殊方法名称<init>,或特殊方法名称<clinit>。
descriptor_indexdescriptor_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构,表示有效的方法描述符(§4.3.3)。此外:
- 如果此方法在类中而不是接口中,且方法名称为
<init>,则描述符必须表示一个void方法。 - 如果方法名称为
<clinit>,则描述符必须表示一个void方法,且在版本号为51.0或以上的类文件中,必须是不带参数的方法。
本规范的未来版本可能要求如果access_flags项中设置了ACC_VARARGS标志,则方法描述符的最后一个参数描述符必须是数组类型。
attributes_countattributes_count项的值指示此方法的附加属性数量。
attributes[]attributes表中的每个值必须是一个attribute_info结构(§4.7)。
一个方法可以具有任意数量的与其关联的可选属性。
本规范定义为出现在method_info结构的attributes表中的属性列于表4.7-C中。
关于定义出现在method_info结构的attributes表中的属性的规则在§4.7中给出。
关于method_info结构的attributes表中非预定义属性的规则在§4.7.1中给出。
4.7 属性
属性用于class文件格式的ClassFile、field_info、method_info、Code_attribute和record_component_info结构中(§4.1、§4.5、§4.6、§4.7.3、§4.7.30)。
所有属性都具有以下通用格式:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}对于所有属性,attribute_name_index项必须是类的常量池中的有效无符号16位索引。attribute_name_index处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示属性的名称。attribute_length项的值指示后续信息的字节长度。该长度不包括包含attribute_name_index和attribute_length项的初始6个字节。
本规范预定义了30个属性。为便于查阅,它们被列出三次:
- 表4.7-A按本章节的属性节号排序。每个属性都显示了首次定义的
class文件格式版本,以及引入该版本的Java SE平台版本(§4.1)。 - 表4.7-B按每个属性首次定义的
class文件格式版本排序。 - 表4.7-C按属性在
class文件中出现的位置排序。
在本规范的使用上下文中,即在其出现的class文件结构的属性表中,这些预定义属性的名称是保留的。
关于预定义属性在属性表中出现的任何条件,都在描述该属性的节中明确规定。如果未指定条件,则该属性可以在属性表中出现任意次数。
预定义属性根据其目的分为三组:
1. 七个属性对Java虚拟机正确解释class文件至关重要:
ConstantValueCodeStackMapTableBootstrapMethodsNestHostNestMembersPermittedSubclasses
在版本号为v的class文件中,如果Java虚拟机实现支持class文件格式版本v,并且该属性在版本v或更早版本中首次定义,并且该属性出现在定义为出现的位置,则必须识别并正确读取这些属性中的每一个。
2. 十个属性对Java虚拟机正确解释class文件不重要,但对Java SE平台类库正确解释class文件至关重要,或者对工具有用(在这种情况下,指定该属性的节将其描述为“可选的”):
ExceptionsInnerClassesEnclosingMethodSyntheticSignatureRecordSourceFileLineNumberTableLocalVariableTableLocalVariableTypeTable
在版本号为v的class文件中,如果Java虚拟机实现支持版本v,并且该属性在版本v或更早版本中首次定义,并且该属性出现在定义为出现的位置,则必须识别并正确读取这些属性中的每一个。
3. 十三个属性对Java虚拟机正确解释class文件不重要,但包含关于class文件的元数据,这些元数据要么由Java SE平台类库公开,要么由工具提供(在这种情况下,指定该属性的节将其描述为“可选的”):
SourceDebugExtensionDeprecatedRuntimeVisibleAnnotationsRuntimeInvisibleAnnotationsRuntimeVisibleParameterAnnotationsRuntimeInvisibleParameterAnnotationsRuntimeVisibleTypeAnnotationsRuntimeInvisibleTypeAnnotationsAnnotationDefaultMethodParametersModuleModulePackagesModuleMainClass
Java虚拟机实现可以使用这些属性包含的信息,否则必须静默忽略这些属性。
表4.7-A. 预定义class文件属性(按节)
| 属性 | 节 | class文件版本 | Java SE版本 |
|---|---|---|---|
ConstantValue | §4.7.2 | 45.3 | 1.0.2 |
Code | §4.7.3 | 45.3 | 1.0.2 |
StackMapTable | §4.7.4 | 50.0 | 6 |
Exceptions | §4.7.5 | 45.3 | 1.0.2 |
InnerClasses | §4.7.6 | 45.3 | 1.1 |
EnclosingMethod | §4.7.7 | 49.0 | 5.0 |
Synthetic | §4.7.8 | 45.3 | 1.1 |
Signature | §4.7.9 | 49.0 | 5.0 |
SourceFile | §4.7.10 | 45.3 | 1.0.2 |
SourceDebugExtension | §4.7.11 | 49.0 | 5.0 |
LineNumberTable | §4.7.12 | 45.3 | 1.0.2 |
LocalVariableTable | §4.7.13 | 45.3 | 1.0.2 |
LocalVariableTypeTable | §4.7.14 | 49.0 | 5.0 |
Deprecated | §4.7.15 | 45.3 | 1.1 |
RuntimeVisibleAnnotations | §4.7.16 | 49.0 | 5.0 |
RuntimeInvisibleAnnotations | §4.7.17 | 49.0 | 5.0 |
RuntimeVisibleParameterAnnotations | §4.7.18 | 49.0 | 5.0 |
RuntimeInvisibleParameterAnnotations | §4.7.19 | 49.0 | 5.0 |
RuntimeVisibleTypeAnnotations | §4.7.20 | 52.0 | 8 |
RuntimeInvisibleTypeAnnotations | §4.7.21 | 52.0 | 8 |
AnnotationDefault | §4.7.22 | 49.0 | 5.0 |
BootstrapMethods | §4.7.23 | 51.0 | 7 |
MethodParameters | §4.7.24 | 52.0 | 8 |
Module | §4.7.25 | 53.0 | 9 |
ModulePackages | §4.7.26 | 53.0 | 9 |
ModuleMainClass | §4.7.27 | 53.0 | 9 |
NestHost | §4.7.28 | 55.0 | 11 |
NestMembers | §4.7.29 | 55.0 | 11 |
Record | §4.7.30 | 60.0 | 16 |
PermittedSubclasses | §4.7.31 | 61.0 | 17 |
表4.7-B. 预定义class文件属性(按class文件版本)
| 属性 | class文件版本 | Java SE版本 | 节 |
|---|---|---|---|
ConstantValue | 45.3 | 1.0.2 | §4.7.2 |
Code | 45.3 | 1.0.2 | §4.7.3 |
Exceptions | 45.3 | 1.0.2 | §4.7.5 |
SourceFile | 45.3 | 1.0.2 | §4.7.10 |
LineNumberTable | 45.3 | 1.0.2 | §4.7.12 |
LocalVariableTable | 45.3 | 1.0.2 | §4.7.13 |
InnerClasses | 45.3 | 1.1 | §4.7.6 |
Synthetic | 45.3 | 1.1 | §4.7.8 |
Deprecated | 45.3 | 1.1 | §4.7.15 |
EnclosingMethod | 49.0 | 5.0 | §4.7.7 |
Signature | 49.0 | 5.0 | §4.7.9 |
SourceDebugExtension | 49.0 | 5.0 | §4.7.11 |
LocalVariableTypeTable | 49.0 | 5.0 | §4.7.14 |
RuntimeVisibleAnnotations | 49.0 | 5.0 | §4.7.16 |
RuntimeInvisibleAnnotations | 49.0 | 5.0 | §4.7.17 |
RuntimeVisibleParameterAnnotations | 49.0 | 5.0 | §4.7.18 |
RuntimeInvisibleParameterAnnotations | 49.0 | 5.0 | §4.7.19 |
AnnotationDefault | 49.0 | 5.0 | §4.7.22 |
StackMapTable | 50.0 | 6 | §4.7.4 |
BootstrapMethods | 51.0 | 7 | §4.7.23 |
RuntimeVisibleTypeAnnotations | 52.0 | 8 | §4.7.20 |
RuntimeInvisibleTypeAnnotations | 52.0 | 8 | §4.7.21 |
MethodParameters | 52.0 | 8 | §4.7.24 |
Module | 53.0 | 9 | §4.7.25 |
ModulePackages | 53.0 | 9 | §4.7.26 |
ModuleMainClass | 53.0 | 9 | §4.7.27 |
NestHost | 55.0 | 11 | §4.7.28 |
NestMembers | 55.0 | 11 | §4.7.29 |
Record | 60.0 | 16 | §4.7.30 |
PermittedSubclasses | 61.0 | 17 | §4.7.31 |
表4.7-C. 预定义class文件属性(按位置)
| 属性 | 位置 |
|---|---|
SourceFile | ClassFile |
InnerClasses | ClassFile |
EnclosingMethod | ClassFile |
SourceDebugExtension | ClassFile |
BootstrapMethods | ClassFile |
Module, ModulePackages, ModuleMainClass | ClassFile |
NestHost, NestMembers | ClassFile |
Record | ClassFile |
PermittedSubclasses | ClassFile |
ConstantValue | field_info |
Code | method_info |
Exceptions | method_info |
RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations | method_info |
AnnotationDefault | method_info |
MethodParameters | method_info |
Synthetic | ClassFile, field_info, method_info |
Deprecated | ClassFile, field_info, method_info |
Signature | ClassFile, field_info, method_info, record_component_info |
RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations | ClassFile, field_info, method_info, record_component_info |
LineNumberTable | Code |
LocalVariableTable | Code |
LocalVariableTypeTable | Code |
StackMapTable | Code |
RuntimeVisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations | ClassFile, field_info, method_info, Code, record_component_info |
4.7.1 定义和命名新属性
编译器被允许定义和发出包含新属性的class文件,这些新属性出现在class文件结构、field_info结构、method_info结构和Code属性(§4.7.3)的属性表中。Java虚拟机实现被允许识别和使用在这些属性表中找到的新属性。但是,任何未作为本规范一部分定义的属性都不得影响class文件的语义。Java虚拟机实现被要求静默忽略它们不认识的属性。
例如,允许定义一个新的属性来支持供应商特定的调试。由于Java虚拟机实现被要求忽略它们不认识的属性,因此为特定Java虚拟机实现设计的class文件可以被其他实现使用,即使这些实现无法利用class文件包含的额外调试信息。
Java虚拟机实现被特别禁止仅仅因为存在某个新属性而抛出异常或以其他方式拒绝使用class文件。当然,如果给定class文件不包含它们所需的所有属性,操作class文件的工具可能无法正常运行。
两个意图不同但恰好使用相同属性名称且长度相同的属性,将在识别任一属性的实现上发生冲突。除本规范之外定义的属性应按照《Java语言规范(Java SE 17版)》(JLS §6.1)中描述的包命名约定来选择名称。
本规范的未来版本可能会定义附加属性。
4.7.2 ConstantValue属性
ConstantValue属性是field_info结构(§4.5)属性表中的固定长度属性。ConstantValue属性表示常量表达式(JLS §15.28)的值,其使用如下:
- 如果
field_info结构的access_flags项中设置了ACC_STATIC标志,则由该field_info结构表示的字段在声明该字段的类或接口初始化期间(§5.5)被赋予其ConstantValue属性所表示的值。这发生在该类或接口的类或接口初始化方法(§2.9.2)被调用之前。 - 否则,Java虚拟机必须静默忽略该属性。
一个field_info结构的属性表中最多只能有一个ConstantValue属性。
ConstantValue属性具有以下格式:
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}ConstantValue_attribute结构的项如下:
attribute_name_indexattribute_name_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示字符串"ConstantValue"。
attribute_lengthattribute_length项的值必须为2。
constantvalue_indexconstantvalue_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目给出此属性所表示的值。该constant_pool条目的类型必须适合该字段,如表4.7.2-A中指定。
表4.7.2-A. 常量值属性类型
| 字段类型 | 条目类型 |
|---|---|
int, short, char, byte, boolean | CONSTANT_Integer |
float | CONSTANT_Float |
long | CONSTANT_Long |
double | CONSTANT_Double |
String | CONSTANT_String |
4.7.3 Code属性
Code属性是method_info结构(§4.6)属性表中的可变长度属性。Code属性包含方法的Java虚拟机指令和辅助信息,包括实例初始化方法和类或接口初始化方法(§2.9.1,§2.9.2)。
如果方法是native或abstract,并且不是类或接口初始化方法,则其method_info结构的属性表中不得有Code属性。否则,其method_info结构的属性表中必须恰好有一个Code属性。
Code属性具有以下格式:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}Code_attribute结构的项如下:
attribute_name_indexattribute_name_index项的值必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Utf8_info结构(§4.4.7),表示字符串"Code"。
attribute_lengthattribute_length项的值指示属性的长度,不包括初始的6个字节。
max_stackmax_stack项的值给出该方法执行期间任何时刻操作数栈(§2.6.2)的最大深度。
max_localsmax_locals项的值给出在调用此方法时分配的局部变量数组中局部变量的数量(§2.6.1),包括用于在调用时向方法传递参数的局部变量。
对于long或double类型的值,最大的局部变量索引为max_locals - 2。对于任何其他类型的值,最大的局部变量索引为max_locals - 1。
code_lengthcode_length项的值给出此方法的code数组中的字节数。
code_length的值必须大于零(因为code数组不能为空)且小于65536。
code[]code数组给出实现该方法的实际Java虚拟机指令字节。
当code数组在字节寻址的机器上读入内存时,如果数组的第一个字节在4字节边界上对齐,则tableswitch和lookupswitch的32位偏移量将是4字节对齐的。(有关code数组对齐影响的更多信息,请参阅这些指令的描述。)
关于code数组内容的详细约束很广泛,并在单独一节(§4.9)中给出。
exception_table_lengthexception_table_length项的值给出exception_table数组中的条目数。
exception_table[]exception_table数组中的每个条目描述code数组中的一个异常处理器。处理程序在exception_table数组中的顺序很重要(§2.10)。
每个exception_table条目包含以下四项:
start_pc, end_pc
start_pc和end_pc两项的值指示异常处理器活动在code数组中的范围。start_pc的值必须是code数组中指令操作码的有效索引。end_pc的值要么必须是code数组中指令操作码的有效索引,要么必须等于code_length(code数组的长度)。start_pc的值必须小于end_pc的值。start_pc是包含的,end_pc是排除的;也就是说,当程序计数器在区间[start_pc, end_pc)内时,异常处理器必须是活动的。end_pc是排除的这一事实是Java虚拟机设计中的历史错误:如果一个方法的Java虚拟机代码恰好是65535字节长,并且以一条1字节长的指令结束,那么该指令不能被异常处理器保护。编译器编写者可以通过将任何方法、实例初始化方法或静态初始化器(任何code数组的大小)的生成Java虚拟机代码的最大大小限制为65534字节来解决此错误。handler_pc
handler_pc项的值指示异常处理器的起始位置。该项的值必须是code数组中的有效索引,并且必须是指令操作码的索引。catch_type 如果
catch_type项的值非零,则它必须是constant_pool表中的有效索引。该索引处的constant_pool条目必须是一个CONSTANT_Class_info结构(§4.4.1),表示此异常处理器被指定捕获的异常类。仅当抛出的异常是给定类或其子类的实例时,才会调用该异常处理器。验证器检查该类是
Throwable或Throwable的子类(§4.9.2)。如果
catch_type项的值为零,则此异常处理器被调用以处理所有异常。这用于实现finally(§3.13)。
attributes_countattributes_count项的值指示Code属性的属性数量。
attributes[]attributes表中的每个值必须是一个attribute_info结构(§4.7)。
一个Code属性可以具有任意数量的与其关联的可选属性。
本规范定义为出现在Code属性的attributes表中的属性列于表4.7-C中。
关于定义出现在Code属性的attributes表中的属性的规则在§4.7中给出。
关于Code属性的attributes表中非预定义属性的规则在§4.7.1中给出。
(注:由于篇幅限制,本节之后的子节§4.7.4至§4.7.31非常详细,但根据您的要求,我们继续提供完整翻译。以下将按顺序翻译每个子节,但为保持可读性,部分重复性内容会适当精简。)
4.7.4 StackMapTable属性
StackMapTable属性是Code属性(§4.7.3)属性表中的可变长度属性。StackMapTable属性在类型检查验证过程(§4.10.1)中使用。
一个Code属性的属性表中最多只能有一个StackMapTable属性。
在版本号为50.0或以上的class文件中,如果方法的Code属性没有StackMapTable属性,则它具有隐式栈映射属性(§4.10.1)。此隐式栈映射属性等效于number_of_entries为零的StackMapTable属性。
StackMapTable属性具有以下格式:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}StackMapTable_attribute结构的项如下:
attribute_name_index 值为表示"StackMapTable"的CONSTANT_Utf8_info索引。
attribute_length 属性长度(不含初始6字节)。
number_of_entriesentries表中的stack_map_frame条目数。
entries[] 每个条目描述方法的一个栈映射帧。帧的顺序很重要。
栈映射帧指定(显式或隐式)其适用的字节码偏移量,以及该偏移量处局部变量和操作数栈条目的验证类型。
entries表中描述的每个栈映射帧都依赖于前一帧的某些语义。方法的第一个栈映射帧是隐式的,由类型检查器从方法描述符计算得出(§4.10.1.6)。因此,entries[0]处的stack_map_frame结构描述的是方法的第二个栈映射帧。
栈映射帧适用的字节码偏移量通过取帧中指定的(显式或隐式)offset_delta值,并将其加上前一帧的字节码偏移量加1来计算,除非前一帧是方法的初始帧。在这种情况下,栈映射帧适用的字节码偏移量就是帧中指定的offset_delta值。
通过使用偏移量增量而不是存储实际的字节码偏移量,我们确保栈映射帧按正确排序。此外,通过对所有显式帧(与隐式第一帧相对)一致使用公式offset_delta + 1,我们保证了没有重复。
我们说字节码中的一条指令有对应的栈映射帧,如果该指令在Code属性的code数组中的偏移量为i,且该Code属性有一个StackMapTable属性,其entries数组包含一个适用于字节码偏移量i的栈映射帧。
验证类型指定一个或两个位置的类型,其中位置是单个局部变量或单个操作数栈条目。验证类型由区分联合verification_type_info表示,由一个1字节标签(指示联合的哪个项在使用中)后跟零个或多个字节(提供关于标签的更多信息)组成。
union verification_type_info {
Top_variable_info;
Integer_variable_info;
Float_variable_info;
Long_variable_info;
Double_variable_info;
Null_variable_info;
UninitializedThis_variable_info;
Object_variable_info;
Uninitialized_variable_info;
}指定局部变量数组或操作数栈中一个位置的验证类型由verification_type_info联合的以下项表示:
Top_variable_info:标签ITEM_Top(0),表示验证类型top。Integer_variable_info:标签ITEM_Integer(1),表示int。Float_variable_info:标签ITEM_Float(2),表示float。Long_variable_info:标签ITEM_Long(4),表示long,占用两个位置。Double_variable_info:标签ITEM_Double(3),表示double,占用两个位置。Null_variable_info:标签ITEM_Null(5),表示null。UninitializedThis_variable_info:标签ITEM_UninitializedThis(6),表示未初始化的this。Object_variable_info:标签ITEM_Object(7),后跟一个cpool_index,表示类引用。Uninitialized_variable_info:标签ITEM_Uninitialized(8),后跟一个offset,表示new指令的偏移量。
栈映射帧由区分联合stack_map_frame表示,由1字节标签(指示联合的哪个项在使用中)后跟零个或多个字节组成。
union stack_map_frame {
same_frame;
same_locals_1_stack_item_frame;
same_locals_1_stack_item_frame_extended;
chop_frame;
same_frame_extended;
append_frame;
full_frame;
}标签指示帧类型:
- same_frame(标签0-63):与前一帧具有完全相同的局部变量,操作数栈为空。
offset_delta为frame_type。 - same_locals_1_stack_item_frame(标签64-127):与前一帧具有相同的局部变量,操作数栈有一个条目。
offset_delta = frame_type - 64。后跟一个验证类型。 - same_locals_1_stack_item_frame_extended(标签247):与前一帧具有相同的局部变量,操作数栈有一个条目。
offset_delta显式给出,后跟一个验证类型。 - chop_frame(标签248-250):与前一帧具有相同的局部变量,但移除了最后
k个局部变量(k = 251 - frame_type),操作数栈为空。offset_delta显式给出。 - same_frame_extended(标签251):与前一帧具有相同的局部变量,操作数栈为空。
offset_delta显式给出。 - append_frame(标签252-254):与前一帧具有相同的局部变量,但新增了
k个局部变量(k = frame_type - 251),操作数栈为空。offset_delta显式给出,后跟k个验证类型。 - full_frame(标签255):完整的帧。
offset_delta显式给出,后跟局部变量数量和验证类型列表,以及操作数栈条目数量和验证类型列表。
具体编码细节和约束详见规范原文,此处不再展开。
4.7.5 Exceptions属性
Exceptions属性是method_info结构(§4.6)属性表中的可变长度属性。Exceptions属性指示方法可能抛出的已检查异常。
一个method_info结构的属性表中最多只能有一个Exceptions属性。
Exceptions属性具有以下格式:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}attribute_name_index:"Exceptions"。 attribute_length:长度(不含初始6字节)。 number_of_exceptions:异常索引表条目数。 exception_index_table[]:每个条目必须是CONSTANT_Class_info结构的索引,表示该方法声明抛出的类类型。
方法应仅在以下三个条件之一成立时抛出异常:异常是RuntimeException或其子类;是Error或其子类;是指定的异常类或其子类。
4.7.6 InnerClasses属性
InnerClasses属性是ClassFile结构(§4.1)属性表中的可变长度属性。如果类或接口c的常量池包含至少一个表示非包成员的类或接口的CONSTANT_Class_info条目,则c的ClassFile结构的属性表中必须恰好有一个InnerClasses属性。
格式:
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{
u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
}每个条目描述一个内部类。inner_class_info_index是内部类的CONSTANT_Class_info索引。outer_class_info_index如果是顶级类、局部类或匿名类则为0,否则为所在类的索引。inner_name_index如果是匿名类则为0,否则为简单名称。inner_class_access_flags指示源中的访问标志。
4.7.7 EnclosingMethod属性
固定长度属性,出现在ClassFile中,仅当类表示局部类或匿名类时才存在。指示最内层的封闭类和方法。
格式:
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index;
u2 method_index;
}class_index为封闭类的索引。如果当前类不是立即被方法或构造函数封闭,则method_index为0;否则为CONSTANT_NameAndType_info索引,表示封闭方法的名称和类型。
4.7.8 Synthetic属性
固定长度属性,出现在ClassFile、field_info或method_info中,指示该成员是由编译器生成的,不在源代码中。但某些编译器生成的成员(如默认构造函数、类初始化方法、枚举和记录类的隐式成员)不需要此属性。
格式:
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}attribute_length为0。
4.7.9 Signature属性
固定长度属性,出现在ClassFile、field_info、method_info或record_component_info中,存储泛型签名信息。
格式:
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}signature_index是CONSTANT_Utf8_info索引,表示类签名、方法签名或字段签名。
(§4.7.9.1 签名的语法规则详见规范原文,此处略。)
4.7.10 SourceFile属性
固定长度属性,出现在ClassFile中,可选。指示源文件名。
格式:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}sourcefile_index为表示源文件名的CONSTANT_Utf8_info索引。
4.7.11 SourceDebugExtension属性
可选属性,出现在ClassFile中,包含扩展调试信息。
格式:
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}debug_extension为修改过的UTF-8字符串,不以空终止。
4.7.12 LineNumberTable属性
可选属性,出现在Code属性中,将代码偏移映射到源行号。
格式:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}每个条目指示从start_pc开始对应新的行号line_number。
4.7.13 LocalVariableTable属性
可选属性,出现在Code中,描述局部变量的信息,用于调试。
格式:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}每个条目表示在[start_pc, start_pc+length)范围内,局部变量index的名称和描述符。
4.7.14 LocalVariableTypeTable属性
与LocalVariableTable类似,但存储的是泛型签名(而非描述符),用于类型变量或参数化类型。
格式类似,使用signature_index代替descriptor_index。
4.7.15 Deprecated属性
固定长度属性,出现在ClassFile、field_info或method_info中,指示已弃用。
格式:
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}attribute_length为0。
4.7.16 RuntimeVisibleAnnotations属性
可变长度属性,出现在ClassFile、field_info、method_info或record_component_info中,存储运行时可见的注解。
格式包含num_annotations和annotation结构数组。每个annotation结构包含type_index(注解类型的描述符)、num_element_value_pairs和元素值对列表。
元素值由element_value联合表示,标签指示类型(如B、C、D等),具体值存储在对应的常量或结构中。
(§4.7.16.1 详细描述了element_value的联合结构。)
4.7.17 RuntimeInvisibleAnnotations属性
与RuntimeVisibleAnnotations类似,但存储运行时不可见的注解,除非实现特定机制指示保留,否则Java虚拟机忽略。
4.7.18 RuntimeVisibleParameterAnnotations属性
出现在method_info中,存储方法参数的运行时可见注解。
格式包含num_parameters,每个参数有num_annotations和注解数组。
4.7.19 RuntimeInvisibleParameterAnnotations属性
与RuntimeVisibleParameterAnnotations类似,但不可见。
4.7.20 RuntimeVisibleTypeAnnotations属性
出现在ClassFile、field_info、method_info、record_component_info或Code中,存储类型注解(如对泛型类型参数、类型使用等)。
结构包含target_type、target_info(指定注解位置)、target_path(指定深层类型路径)以及注解内容。
(§4.7.20.1 和 §4.7.20.2 详细描述了target_info和type_path。)
4.7.21 RuntimeInvisibleTypeAnnotations属性
与RuntimeVisibleTypeAnnotations类似,但不可见。
4.7.22 AnnotationDefault属性
出现在method_info中,记录注解接口元素的默认值。
格式包含一个element_value结构。
4.7.23 BootstrapMethods属性
出现在ClassFile中,记录引导方法,用于动态计算常量和调用点。
格式包含num_bootstrap_methods,每个引导方法指定bootstrap_method_ref(方法句柄)、num_bootstrap_arguments和参数索引数组。
4.7.24 MethodParameters属性
出现在method_info中,记录方法形式参数的信息(如名称和访问标志)。
格式:
MethodParameters_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 parameters_count;
{
u2 name_index;
u2 access_flags;
} parameters[parameters_count];
}parameters_count应与方法描述符中的参数数量一致。每个条目包含名称索引和标志(ACC_FINAL、ACC_SYNTHETIC、ACC_MANDATED)。
4.7.25 Module属性
出现在ClassFile中(仅当ACC_MODULE设置),描述模块的名称、版本、依赖(requires)、导出(exports)、开放(opens)、使用服务(uses)和提供服务(provides)。
格式包含module_name_index、module_flags、module_version_index、requires_count及requires表、exports_count及exports表、opens_count及opens表、uses_count及uses_index表、provides_count及provides表。
每个requires条目包括requires_index(模块引用)、requires_flags(ACC_TRANSITIVE、ACC_STATIC_PHASE等)和requires_version_index。exports和opens条目类似,包括包索引、标志和限定模块列表。
4.7.26 ModulePackages属性
出现在ClassFile中,列出模块中的所有包(包括导出的、开放的和包含服务实现的)。
格式:
ModulePackages_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 package_count;
u2 package_index[package_count];
}package_index数组包含CONSTANT_Package_info索引。
4.7.27 ModuleMainClass属性
固定长度属性,出现在ClassFile中,指示模块的主类。
格式:
ModuleMainClass_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 main_class_index;
}main_class_index为CONSTANT_Class_info索引。
4.7.28 NestHost属性
固定长度属性,出现在ClassFile中,记录当前类或接口所属的nest的主机。
格式:
NestHost_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 host_class_index;
}host_class_index为CONSTANT_Class_info索引。
4.7.29 NestMembers属性
可变长度属性,出现在ClassFile中,记录被授权成为当前类或接口所托管nest成员的类和接口。
格式:
NestMembers_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
u2 classes[number_of_classes];
}classes数组包含CONSTANT_Class_info索引。
4.7.30 Record属性
可变长度属性,出现在ClassFile中,指示当前类是记录类(record),并存储其组件信息。
格式:
Record_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 components_count;
record_component_info components[components_count];
}每个record_component_info包含name_index、descriptor_index和属性表,属性表可包含Signature、RuntimeVisibleAnnotations等。
4.7.31 PermittedSubclasses属性
可变长度属性,出现在ClassFile中,记录被授权直接扩展或实现当前类或接口的类和接口(用于sealed类)。
格式:
PermittedSubclasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
u2 classes[number_of_classes];
}classes数组包含CONSTANT_Class_info索引。如果类设置了ACC_FINAL,则不能有此属性;反之亦然。
4.8 格式检查
当潜在的class文件被Java虚拟机加载时(§5.3),Java虚拟机首先确保该文件具有class文件的基本格式(§4.1)。此过程称为格式检查。检查如下:
- 前4个字节必须包含正确的魔数。
- 所有预定义属性(§4.7)必须具有适当的长度,除了
StackMapTable、RuntimeVisibleAnnotations、RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations、RuntimeInvisibleParameterAnnotations、RuntimeVisibleTypeAnnotations、RuntimeInvisibleTypeAnnotations、AnnotationDefault。 class文件不得被截断或在末尾有多余字节。- 常量池必须满足§4.4中记录的约束。
例如,常量池中的每个CONSTANT_Class_info结构在其name_index项中必须包含一个有效常量池索引,指向一个CONSTANT_Utf8_info结构。
常量池中的所有字段引用和方法引用必须具有有效的名称、类和描述符(§4.3)。
格式检查不确保给定的字段或方法实际存在于给定类中,也不确保给定的描述符引用真实的类。格式检查仅确保这些项是良构的。更详细的检查在验证字节码本身时以及在解析期间执行。
这些对基本class文件完整性的检查对于解释class文件内容是必要的。格式检查与字节码验证不同,尽管历史上它们被混淆,因为两者都是一种完整性检查形式。
4.9 Java虚拟机代码的约束
方法、实例初始化方法(§2.9.1)或类/接口初始化方法(§2.9.2)的代码存储在class文件的method_info结构的Code属性(§4.7.3)的code数组中。本节描述与Code_attribute结构内容相关的约束。
4.9.1 静态约束
class文件的静态约束是定义文件良构性的约束。前面的章节已经给出了这些约束,除了class文件中代码的静态约束。class文件中代码的静态约束指定了Java虚拟机指令在code数组中必须如何布局,以及各个指令的操作数必须是什么。
code数组中指令的静态约束如下:
code数组中只能出现§6.5中记录的指令实例。使用保留操作码(§6.2)或本规范中未记录的任何操作码的指令实例不得出现在code数组中。- 如果
class文件版本号为51.0或以上,则使用jsr、jsr_w或ret操作码的指令实例不得出现在code数组中。 code数组中第一条指令的操作码从索引0开始。- 对于
code数组中除最后一条指令外的每条指令,下一条指令的操作码索引等于当前指令的操作码索引加上该指令的长度(包括其所有操作数)。wide指令在这些目的下与其他指令一样被对待;wide指令要修改的操作码被视为该wide指令的操作数之一。该操作码必须永远不能通过计算直接到达。 code数组中最后一条指令的最后一个字节必须是索引code_length - 1处的字节。
code数组中指令操作数的静态约束如下:
- 每个跳转和分支指令(
jsr、jsr_w、goto、goto_w、ifeq、ifne、ifle、iflt、ifge、ifgt、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmple、if_icmplt、if_icmpge、if_icmpgt、if_acmpeq、if_acmpne)的目标必须是此方法内指令的操作码。 - 跳转或分支指令的目标绝不能是用于指定
wide指令要修改的操作的操作码;跳转或分支目标可以是wide指令本身。 - 每个
tableswitch指令的每个目标(包括默认目标)必须是此方法内指令的操作码。 - 每个
tableswitch指令在其跳转表中的条目数必须与其low和high跳转表操作数的值一致,且其low值必须小于或等于其high值。 tableswitch指令的任何目标都不能是用于指定wide指令要修改的操作的操作码;tableswitch目标可以是wide指令本身。- 每个
lookupswitch指令的每个目标(包括默认目标)必须是此方法内指令的操作码。 - 每个
lookupswitch指令的匹配偏移对的数量必须与其npairs操作数的值一致。匹配偏移对必须按匹配值的符号数值升序排序。 lookupswitch指令的任何目标都不能是用于指定wide指令要修改的操作的操作码;lookupswitch目标可以是wide指令本身。- 每条
ldc指令和每条ldc_w指令的操作数必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是可加载的(§4.4),且不能是以下任何一种:CONSTANT_Long或CONSTANT_Double类型的条目。- 引用的
CONSTANT_NameAndType_info结构指示描述符为J(long)或D(double)的CONSTANT_Dynamic条目。
- 每条
ldc2_w指令的操作数必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是可加载的,并且特别是以下之一:CONSTANT_Long或CONSTANT_Double类型的条目。- 引用的
CONSTANT_NameAndType_info结构指示描述符为J(long)或D(double)的CONSTANT_Dynamic条目。 - 后续的常量池索引也必须是常量池中的有效索引,且该索引处的常量池条目不得被使用。
- 每条
getfield、putfield、getstatic和putstatic指令的操作数必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是CONSTANT_Fieldref类型。 - 每条
invokevirtual指令的indexbyte操作数必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是CONSTANT_Methodref类型。 - 每条
invokespecial和invokestatic指令的indexbyte操作数必须表示constant_pool表中的有效索引。如果class文件版本号小于52.0,则该索引引用的常量池条目必须是CONSTANT_Methodref类型;如果版本号为52.0或以上,则必须是CONSTANT_Methodref或CONSTANT_InterfaceMethodref类型。 - 每条
invokeinterface指令的indexbyte操作数必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是CONSTANT_InterfaceMethodref类型。- 每条
invokeinterface指令的count操作数的值必须反映存储要传递给接口方法的参数所需的局部变量数量,该数量由CONSTANT_InterfaceMethodref常量池条目引用的CONSTANT_NameAndType_info结构的描述符隐含。 - 每条
invokeinterface指令的第四个操作数字节必须为零。
- 每条
- 每条
invokedynamic指令的indexbyte操作数必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是CONSTANT_InvokeDynamic类型。- 每条
invokedynamic指令的第三和第四个操作数字节必须为零。
- 每条
- 只有
invokespecial指令被允许调用实例初始化方法(§2.9.1)。- 没有其他名称以字符
'<'('\u003c')开头的方法可以被方法调用指令调用。特别是,特殊命名的类或接口初始化方法<clinit>永远不会从Java虚拟机指令中显式调用,而只能由Java虚拟机本身隐式调用。
- 没有其他名称以字符
- 每条
instanceof、checkcast、new和anewarray指令的操作数,以及每条multianewarray指令的indexbyte操作数,必须表示constant_pool表中的有效索引。该索引引用的常量池条目必须是CONSTANT_Class类型。 - 任何
new指令都不得引用表示数组类型(§4.3.2)的CONSTANT_Class类型常量池条目。new指令不能用于创建数组。 - 任何
anewarray指令都不得用于创建维度超过255的数组。 multianewarray指令只能用于创建其类型维度至少与其dimensions操作数值一样大的数组。也就是说,虽然multianewarray指令不要求创建其indexbyte操作数引用的数组类型的所有维度,但它不得尝试创建比数组类型中更多的维度。- 每条
multianewarray指令的dimensions操作数不得为零。
- 每条
- 每条
newarray指令的atype操作数必须取以下值之一:T_BOOLEAN(4)、T_CHAR(5)、T_FLOAT(6)、T_DOUBLE(7)、T_BYTE(8)、T_SHORT(9)、T_INT(10)或T_LONG(11)。 - 每条
iload、fload、aload、istore、fstore、astore、iinc和ret指令的索引操作数必须是非负整数,且不大于max_locals - 1。- 每条
iload_<n>、fload_<n>、aload_<n>、istore_<n>、fstore_<n>和astore_<n>指令的隐式索引必须不大于max_locals - 1。
- 每条
- 每条
lload、dload、lstore和dstore指令的索引操作数必须不大于max_locals - 2。- 每条
lload_<n>、dload_<n>、lstore_<n>和dstore_<n>指令的隐式索引必须不大于max_locals - 2。
- 每条
- 每条修改
iload、fload、aload、istore、fstore、astore、iinc或ret指令的wide指令的indexbyte操作数必须表示不大于max_locals - 1的非负整数。 - 每条修改
lload、dload、lstore或dstore指令的wide指令的indexbyte操作数必须表示不大于max_locals - 2的非负整数。
4.9.2 结构约束
code数组的结构约束指定了Java虚拟机指令之间的关系。结构约束如下:
- 无论导致其调用的执行路径如何,每条指令都必须在操作数栈和局部变量数组中具有适当类型和数量的参数才能执行。
- 操作
int类型值的指令也允许操作boolean、byte、char和short类型的值。 - 如§2.3.4和§2.11.1所述,Java虚拟机内部将
boolean、byte、short和char类型的值转换为int类型。
- 操作
- 如果一条指令可以沿着多条不同的执行路径执行,则无论路径如何,在执行该指令之前,操作数栈必须具有相同的深度(§2.6.2)。
- 在执行过程中,操作数栈的深度在任何时候都不得超过
max_stack项隐含的深度。 - 在执行过程中,任何时候从操作数栈弹出的值都不能超过其中包含的值。
- 在执行过程中,持有
long或double类型值的局部变量对的顺序不得颠倒或拆分。任何时候都不得单独操作该对的局部变量。 - 在赋值之前,不得访问任何局部变量(或
long/double类型的局部变量对)。 - 每条
invokespecial指令必须命名以下之一:- 实例初始化方法(§2.9.1)
- 当前类或接口中的方法
- 当前类超类中的方法
- 当前类或接口的直接超接口中的方法
Object中的方法
- 如果
invokespecial指令命名了实例初始化方法,则操作数栈上的目标引用必须是一个未初始化的类实例。- 绝不能在已初始化的类实例上调用实例初始化方法。此外:
- 如果操作数栈上的目标引用是当前类的未初始化类实例,则
invokespecial必须命名来自当前类或其直接超类的实例初始化方法。 - 如果
invokespecial指令命名了实例初始化方法,且操作数栈上的目标引用是由之前的new指令创建的类实例,则invokespecial必须命名来自该类实例的类的实例初始化方法。
- 如果操作数栈上的目标引用是当前类的未初始化类实例,则
- 绝不能在已初始化的类实例上调用实例初始化方法。此外:
- 如果
invokespecial指令命名的方法不是实例初始化方法,则操作数栈上的目标引用必须是一个类实例,其类型与当前类赋值兼容(JLS §5.2)。invokespecial的一般规则是,invokespecial命名的类或接口必须“高于”调用者类或接口,而invokespecial的目标接收者对象必须“处于”或“低于”调用者类或接口。后一条款尤为重要:一个类或接口只能对其自身的对象执行invokespecial。关于后一条款如何在Prolog中实现的说明,请参见§invokespecial。
- 每个实例初始化方法,除了派生自
Object类构造函数的实例初始化方法外,必须在其实例成员被访问之前调用this的另一个实例初始化方法或其直接超类super的实例初始化方法。- 但是,在调用任何实例初始化方法之前,可以通过
putfield对当前类中声明的实例字段进行赋值。
- 但是,在调用任何实例初始化方法之前,可以通过
- 当调用任何实例方法或访问任何实例变量时,包含该实例方法或实例变量的类实例必须已经初始化。
- 如果异常处理器保护的代码中的局部变量中存在未初始化的类实例,则(i)如果处理器位于
<init>方法内,处理器必须抛出异常或永远循环;并且(ii)如果处理器不在<init>方法内,未初始化的类实例必须保持未初始化。 - 在执行
jsr或jsr_w指令时,操作数栈或局部变量中绝不能有未初始化的类实例。 - 作为方法调用指令目标(即操作数栈上的目标引用的类型)的每个类实例的类型必须与指令中指定的类或接口类型赋值兼容。
- 每次方法调用的参数类型必须与方法描述符方法调用兼容(JLS §5.3,§4.3.3)。
- 每条
return指令必须匹配其方法的返回类型:- 如果方法返回
boolean、byte、char、short或int,只能使用ireturn指令。 - 如果方法返回
float、long或double,则只能分别使用freturn、lreturn或dreturn指令。 - 如果方法返回引用类型,只能使用
areturn指令,并且返回值的类型必须与方法返回描述符赋值兼容(§4.3.3)。 - 所有实例初始化方法、类或接口初始化方法以及声明返回
void的方法只能使用return指令。
- 如果方法返回
- 由
getfield指令访问或由putfield指令修改的每个类实例的类型(即操作数栈上目标引用的类型)必须与指令中指定的类类型赋值兼容。 - 由
putfield或putstatic指令存储的每个值的类型必须与所存储的类实例或类的字段描述符(§4.3.2)兼容:- 如果描述符类型是
boolean、byte、char、short或int,则值必须为int。 - 如果描述符类型是
float、long或double,则值必须分别是float、long或double。 - 如果描述符类型是引用类型,则值的类型必须与描述符类型赋值兼容。
- 如果描述符类型是
- 由
aastore指令存储到数组中的每个值的类型必须是引用类型。aastore指令要存储到的数组的组件类型也必须是引用类型。
- 每条
athrow指令只能抛出Throwable类或其子类的实例。- 方法
Code_attribute结构的exception_table数组的catch_type项中提到的每个类必须是Throwable或Throwable的子类。
- 方法
- 如果
getfield或putfield用于访问声明在不同运行时包的超类中的protected字段,则正在访问的类实例的类型(即操作数栈上目标引用的类型)必须与当前类赋值兼容。- 如果
invokevirtual或invokespecial用于访问声明在不同运行时包的超类中的protected方法,则正在访问的类实例的类型(即操作数栈上目标引用的类型)必须与当前类赋值兼容。
- 如果
- 执行不得从
code数组底部掉出。 - 不得从局部变量加载
returnAddress类型的值。 - 每条
jsr或jsr_w指令后面的指令只能由单个ret指令返回。 - 如果子程序已经存在于子程序调用链中,则返回到的
jsr或jsr_w指令不得用于递归调用该子程序。(当从finally子句内部使用try-finally结构时,子程序可以嵌套。) - 每个
returnAddress类型的实例最多只能返回一次。 - 如果
ret指令返回到子程序调用链中高于与该returnAddress实例对应的ret指令的点,则该实例永远不能用作返回地址。
4.10 class文件的验证
尽管Java编程语言的编译器必须只生成满足前面各节所有静态和结构约束的class文件,但Java虚拟机并不能保证它被要求加载的任何文件都是由该编译器生成的或格式正确。诸如Web浏览器之类的应用程序不会下载源代码然后编译;这些应用程序下载已经编译好的class文件。浏览器需要确定class文件是由可信的编译器生成的,还是由试图利用Java虚拟机的对手生成的。
编译时检查的另一个问题是版本偏差。用户可能已经成功地编译了一个类(例如PurchaseStockOptions)作为TradingClass的子类。但自从该类编译以来,TradingClass的定义可能已经以与先前存在的二进制文件不兼容的方式发生了改变。方法可能已被删除,或者其返回类型或修饰符已更改。字段可能已更改类型,或从实例变量更改为类变量。方法或变量的访问修饰符可能已从public更改为private。关于这些问题的讨论,请参见《Java语言规范(Java SE 17版)》第13章“二进制兼容性”。
由于这些潜在问题,Java虚拟机需要自行验证它试图加载的class文件是否满足必要的约束。Java虚拟机实现在链接时(§5.4)验证每个class文件满足必要的约束。
链接时验证提高了运行时解释器的性能。否则必须在运行时为每条解释的指令执行以验证约束的昂贵检查可以被消除。Java虚拟机可以假设这些检查已经执行。例如,Java虚拟机已经知道以下内容:
- 没有操作数栈溢出或下溢。
- 所有局部变量的使用和存储都是有效的。
- 所有Java虚拟机指令的参数都是有效类型。
Java虚拟机实现可以使用两种验证策略:
- 类型检查验证必须用于验证版本号为50.0或以上的
class文件。 - 类型推断验证必须被所有Java虚拟机实现支持(除了那些符合Java ME CLDC和Java Card规范的实现),以便验证版本号低于50.0的
class文件。
在支持Java ME CLDC和Java Card规范的Java虚拟机实现上的验证由各自的规范管辖。
在两种策略中,验证主要关注对Code属性(§4.7.3)的code数组实施§4.9中的静态和结构约束。然而,在Code属性之外还有三个额外的检查必须在验证期间执行:
- 确保
final类不被子类化。 - 确保
final方法不被覆盖(§5.4.5)。 - 检查每个类(除
Object外)都有一个直接超类。
4.10.1 类型检查验证
版本号为50.0或以上(§4.1)的class文件必须使用本节给出的类型检查规则进行验证。
如果且仅当一个class文件的版本号等于50.0,如果类型检查失败,Java虚拟机实现可以选择尝试执行类型推断验证(§4.10.2)。
这是一个务实的调整,旨在简化向新验证规范的过渡。许多操作
class文件的工具可能会以需要调整方法的栈映射帧的方式修改方法的字节码。如果工具未对栈映射帧进行必要的调整,即使字节码原则上是有效的(因此会在旧的类型推断方案下通过验证),类型检查也可能会失败。为了给工具实现者时间调整其工具,Java虚拟机实现可以回退到旧的验证规范,但仅限于有限的时间。
在类型检查失败但调用了类型推断并成功的情况下,预期会有一定的性能损失。这种损失是不可避免的。它也应作为向工具供应商发出的信号,表明其输出需要调整,并为供应商提供额外的动力来进行这些调整。
总之,回退到类型推断验证既支持逐步向Java SE平台添加栈映射帧(如果版本50.0的class文件中不存在它们,则允许回退),也支持逐步从Java SE平台移除jsr和jsr_w指令(如果它们存在于版本50.0的class文件中,则允许回退)。
如果Java虚拟机实现曾经尝试对版本50.0的class文件执行类型推断验证,它必须在类型检查验证失败的所有情况下都这样做。
这意味着Java虚拟机实现不能在一次情况下选择求助于类型推断而在另一次情况下不这样做。它必须要么拒绝未通过类型检查验证的
class文件,要么在类型检查失败时始终回退到类型推断验证器。
类型检查器强制执行类型规则,这些规则通过Prolog子句指定。英文文本以非正式方式描述类型规则,而Prolog子句提供形式化规范。
类型检查器要求每个带有Code属性(§4.7.3)的方法都有一个栈映射帧列表。栈映射帧列表由Code属性的StackMapTable属性(§4.7.4)给出。其意图是栈映射帧必须出现在方法的每个基本块的开头。栈映射帧指定每个基本块开始时每个操作数栈条目和每个局部变量的验证类型。类型检查器读取每个带有Code属性的方法的栈映射帧,并使用这些映射生成Code属性中指令类型安全性的证明。
如果一个类的所有方法都是类型安全的,并且它没有子类化一个final类,则该类是类型安全的。
classIsTypeSafe(Class) :-
classClassName(Class, Name),
classDefiningLoader(Class, L),
superclassChain(Name, L, Chain),
Chain \= [],
classSuperClassName(Class, SuperclassName),
loadedClass(SuperclassName, L, Superclass),
classIsNotFinal(Superclass),
classMethods(Class, Methods),
checklist(methodIsTypeSafe(Class), Methods).
classIsTypeSafe(Class) :-
classClassName(Class, 'java/lang/Object'),
classDefiningLoader(Class, L),
isBootstrapLoader(L),
classMethods(Class, Methods),
checklist(methodIsTypeSafe(Class), Methods).Prolog谓词classIsTypeSafe假设Class是一个表示已成功解析和加载的二进制类的Prolog项。本规范不强制要求该项的确切结构,但确实要求在其上定义某些谓词。
例如,我们假设一个谓词classMethods(Class, Methods),给定一个如上所述的表示类的项作为其第一个参数,将其第二个参数绑定到包含该类的所有方法的列表,以稍后描述的方便形式表示。
如果谓词classIsTypeSafe不为真,则类型检查器必须抛出异常VerifyError以指示class文件格式错误。否则,class文件已成功通过类型检查,字节码验证已成功完成。
本节的其余部分详细解释了类型检查的过程:
- 首先,我们给出核心Java虚拟机工件(如类和方法)的Prolog谓词(§4.10.1.1)。
- 其次,我们指定类型检查器所知的类型系统(§4.10.1.2)。
- 第三,我们指定指令和栈映射帧的Prolog表示(§4.10.1.3、§4.10.1.4)。
- 第四,我们指定如何类型检查方法,对于没有代码的方法(§4.10.1.5)和带有代码的方法(§4.10.1.6)。
- 第五,我们讨论所有加载和存储指令共有的类型检查问题(§4.10.1.7),以及对
protected成员的访问问题(§4.10.1.8)。 - 最后,我们指定类型检查每条指令的规则(§4.10.1.9)。
4.10.1.1 Java虚拟机工件的访问器
我们规定存在28个Prolog谓词(“访问器”),它们具有某些预期行为,但其形式化定义未在本规范中给出。
classClassName(Class, ClassName):提取类Class的名称ClassName。classIsInterface(Class):当且仅当Class是一个接口时为真。classIsNotFinal(Class):当且仅当Class不是final类时为真。classSuperClassName(Class, SuperClassName):提取类Class的超类名称SuperClassName。classInterfaces(Class, Interfaces):提取类Class的直接超接口列表Interfaces。classMethods(Class, Methods):提取类Class中声明的方法列表Methods。classAttributes(Class, Attributes):提取类Class的属性列表Attributes,每个属性表示为attribute(AttributeName, AttributeContents)。classDefiningLoader(Class, Loader):提取类Class的定义类加载器Loader。isBootstrapLoader(Loader):当且仅当类加载器Loader是引导类加载器时为真。loadedClass(Name, InitiatingLoader, ClassDefinition):当且仅当存在一个名为Name的类,其由类加载器InitiatingLoader加载时符合本规范的表示为ClassDefinition。methodName(Method, Name):提取方法Method的名称Name。methodAccessFlags(Method, AccessFlags):提取方法Method的访问标志AccessFlags。methodDescriptor(Method, Descriptor):提取方法Method的描述符Descriptor。methodAttributes(Method, Attributes):提取方法Method的属性列表Attributes。isInit(Method):当且仅当Method是<init>时为真。isNotInit(Method):当且仅当Method不是<init>时为真。isNotFinal(Method, Class):当且仅当Method在Class中不是final时为真。isStatic(Method, Class):当且仅当Method在Class中是static时为真。isNotStatic(Method, Class):当且仅当Method在Class中不是static时为真。isPrivate(Method, Class):当且仅当Method在Class中是private时为真。isNotPrivate(Method, Class):当且仅当Method在Class中不是private时为真。isProtected(MemberClass, MemberName, MemberDescriptor):当且仅当在MemberClass中有一个名为MemberName、描述符为MemberDescriptor的成员且它是protected时为真。isNotProtected(MemberClass, MemberName, MemberDescriptor):类似,但不是protected。parseFieldDescriptor(Descriptor, Type):将字段描述符Descriptor转换为相应的验证类型Type(§4.10.1.2)。parseMethodDescriptor(Descriptor, ArgTypeList, ReturnType):将方法描述符Descriptor转换为参数验证类型列表ArgTypeList和返回验证类型ReturnType。parseCodeAttribute(Class, Method, FrameSize, MaxStack, ParsedCode, Handlers, StackMap):提取方法Method在Class中的指令流ParsedCode、最大操作数栈大小MaxStack、最大局部变量数FrameSize、异常处理器Handlers和栈映射StackMap。samePackageName(Class1, Class2):当且仅当Class1和Class2的包名相同。differentPackageName(Class1, Class2):当且仅当Class1和Class2的包名不同。
在类型检查方法体时,方便访问有关方法的信息。为此,我们定义了一个环境,一个六元组,包含:
- 一个类
- 一个方法
- 方法的声明返回类型
- 方法中的指令
- 操作数栈的最大大小
- 异常处理器列表
我们指定访问器来从环境中提取信息。
allInstructions(Environment, Instructions) :-
Environment = environment(_Class, _Method, _ReturnType, Instructions, _, _).
exceptionHandlers(Environment, Handlers) :-
Environment = environment(_Class, _Method, _ReturnType, _Instructions, _, Handlers).
maxOperandStackLength(Environment, MaxStack) :-
Environment = environment(_Class, _Method, _ReturnType, _Instructions, MaxStack, _Handlers).
thisClass(Environment, class(ClassName, L)) :-
Environment = environment(Class, _Method, _ReturnType, _Instructions, _, _),
classDefiningLoader(Class, L),
classClassName(Class, ClassName).
thisMethodReturnType(Environment, ReturnType) :-
Environment = environment(_Class, _Method, ReturnType, _Instructions, _, _).我们还指定了额外的谓词来提取环境中的更高级别信息。
offsetStackFrame(Environment, Offset, StackFrame) :-
allInstructions(Environment, Instructions),
member(stackMap(Offset, StackFrame), Instructions).
currentClassLoader(Environment, Loader) :-
thisClass(Environment, class(_, Loader)).最后,我们指定了整个类型规则中使用的一个通用谓词:
notMember(_, []).
notMember(X, [A | More]) :- X \= A, notMember(X, More).指导哪些访问器被规定、哪些被完全指定的原则是,我们不想过度指定class文件的表示。提供对Class或Method项的具体访问器将迫使我们完全指定用于表示class文件的Prolog项格式。
4.10.1.2 验证类型系统
类型检查器强制实施基于验证类型层次结构的类型系统。
验证类型层次结构:
top
_____/\_____
/ \
/ \
oneWord twoWord
/ | \ / \
/ | \ / \
int float reference long double
/ \
/ \
/ \
uninitialized +------------------+
/ \ | Java引用类型 |
/ \ | 层次结构 |
uninitializedThis uninitialized(Offset)
|
null大多数验证类型与表4.3-A中字段描述符表示的原始类型和引用类型有直接对应关系:
- 原始类型
double、float、int和long(字段描述符D、F、I、J)各自对应同名验证类型。 - 原始类型
byte、char、short和boolean(字段描述符B、C、S、Z)都对应验证类型int。 - 类和接口类型(字段描述符以
L开头)对应使用函子class的验证类型。验证类型class(N, L)表示二进制名称为N、由加载器L加载的类。注意,L是类class(N, L)的发起加载器(§5.3),可能不一定是类的定义加载器。- 例如,类类型
Object表示为class('java/lang/Object', BL),其中BL是引导加载器。
- 例如,类类型
- 数组类型(字段描述符以
[开头)对应使用函子arrayOf的验证类型。注意,原始类型byte、char、short和boolean本身不对应验证类型,但其元素类型为这些类型的数组类型对应验证类型;这些验证类型支持baload、bastore、caload、castore、saload、sastore和newarray指令。arrayOf(T)表示组件类型为验证类型T的数组类型。arrayOf(byte)表示元素类型为byte的数组类型。- 同理
char、short、boolean。 - 例如,
int[]和Object[]分别表示为arrayOf(int)和arrayOf(class('java/lang/Object', BL))。byte[]和boolean[][]分别表示为arrayOf(byte)和arrayOf(arrayOf(boolean))。
其余验证类型描述如下:
- 验证类型
top、oneWord、twoWord和reference在Prolog中表示为原子,其名称即验证类型名称。 - 验证类型
uninitialized(Offset)通过将函子uninitialized应用于表示Offset数值的参数来表示。
验证类型的子类型规则如下。
子类型是自反的。
isAssignable(X, X).非Java编程语言引用类型的验证类型具有以下形式的子类型规则:
isAssignable(v, X) :- isAssignable(the_direct_supertype_of_v, X).即v是X的子类型,如果v的直接超类型是X的子类型。规则为:
isAssignable(oneWord, top).
isAssignable(twoWord, top).
isAssignable(int, X) :- isAssignable(oneWord, X).
isAssignable(float, X) :- isAssignable(oneWord, X).
isAssignable(long, X) :- isAssignable(twoWord, X).
isAssignable(double, X) :- isAssignable(twoWord, X).
isAssignable(reference, X) :- isAssignable(oneWord, X).
isAssignable(class(_, _), X) :- isAssignable(reference, X).
isAssignable(arrayOf(_), X) :- isAssignable(reference, X).
isAssignable(uninitialized, X) :- isAssignable(reference, X).
isAssignable(uninitializedThis, X) :- isAssignable(uninitialized, X).
isAssignable(uninitialized(_), X) :- isAssignable(uninitialized, X).
isAssignable(null, class(_, _)).
isAssignable(null, arrayOf(_)).
isAssignable(null, X) :- isAssignable(class('java/lang/Object', BL), X), isBootstrapLoader(BL).这些子类型规则不一定是子类型最明显的表述。在Java编程语言引用类型的子类型规则和其余验证类型的规则之间存在明显的分离。这种分离允许我们表述Java编程语言引用类型与其他验证类型之间的一般子类型关系。这些关系独立于Java引用类型在类型层次结构中的位置,并有助于防止Java虚拟机实现进行过多的类加载。例如,我们不希望为了响应诸如class(foo, L) <: twoWord之类的查询而开始向上爬Java超类层次结构。
我们还有一个规则表示子类型是自反的,因此这些规则共同覆盖了大多数非Java编程语言引用类型的验证类型。
Java编程语言中引用类型的子类型规则通过isJavaAssignable递归指定。
isAssignable(class(X, Lx), class(Y, Ly)) :-
isJavaAssignable(class(X, Lx), class(Y, Ly)).
isAssignable(arrayOf(X), class(Y, L)) :-
isJavaAssignable(arrayOf(X), class(Y, L)).
isAssignable(arrayOf(X), arrayOf(Y)) :-
isJavaAssignable(arrayOf(X), arrayOf(Y)).对于赋值,接口被视为Object。
isJavaAssignable(class(_, _), class(To, L)) :-
loadedClass(To, L, ToClass),
classIsInterface(ToClass).
isJavaAssignable(From, To) :-
isJavaSubclassOf(From, To).数组类型是Object的子类型。意图也是数组类型是Cloneable和java.io.Serializable的子类型。
isJavaAssignable(arrayOf(_), class('java/lang/Object', BL)) :-
isBootstrapLoader(BL).
isJavaAssignable(arrayOf(_), X) :-
isArrayInterface(X).
isArrayInterface(class('java/lang/Cloneable', BL)) :-
isBootstrapLoader(BL).
isArrayInterface(class('java/io/Serializable', BL)) :-
isBootstrapLoader(BL).基本类型数组之间的子类型是同一性关系。
isJavaAssignable(arrayOf(X), arrayOf(Y)) :-
atom(X), atom(Y), X = Y.引用类型数组之间的子类型是协变的。
isJavaAssignable(arrayOf(X), arrayOf(Y)) :-
compound(X), compound(Y), isJavaAssignable(X, Y).子类化是自反的。
isJavaSubclassOf(class(SubclassName, L), class(SubclassName, L)).
isJavaSubclassOf(class(SubclassName, LSub), class(SuperclassName, LSuper)) :-
superclassChain(SubclassName, LSub, Chain),
member(class(SuperclassName, L), Chain),
loadedClass(SuperclassName, L, Sup),
loadedClass(SuperclassName, LSuper, Sup).
superclassChain(ClassName, L, [class(SuperclassName, Ls) | Rest]) :-
loadedClass(ClassName, L, Class),
classSuperClassName(Class, SuperclassName),
classDefiningLoader(Class, Ls),
superclassChain(SuperclassName, Ls, Rest).
superclassChain('java/lang/Object', L, []) :-
loadedClass('java/lang/Object', L, Class),
classDefiningLoader(Class, BL),
isBootstrapLoader(BL).4.10.1.3 指令表示
单个字节码指令在Prolog中表示为项,其函子是指令名称,其参数是解析后的操作数。
例如,aload指令表示为aload(N),包含索引N作为操作数。
指令整体表示为术语列表:instruction(Offset, AnInstruction)。例如,instruction(21, aload(1))。指令在列表中的顺序必须与class文件中的顺序相同。
某些指令的操作数引用常量池中表示字段、方法和动态调用点的条目。此类条目表示为函子应用:
field(FieldClassName, FieldName, FieldDescriptor):对于CONSTANT_Fieldref_info条目。method(MethodClassName, MethodName, MethodDescriptor):对于CONSTANT_Methodref_info条目。imethod(MethodIntfName, MethodName, MethodDescriptor):对于CONSTANT_InterfaceMethodref_info条目。dmethod(CallSiteName, MethodDescriptor):对于CONSTANT_InvokeDynamic_info条目。
为清晰起见,我们假设字段和方法描述符(§4.3.2、§4.3.3)被映射为更易读的名称:类名称去掉前导L和尾随;,基本类型字符映射到类型名称。
例如,getfield指令的操作数引用常量池中表示类Bar中类型为F的字段foo的条目,表示为getfield(field('Bar', 'foo', 'F'))。
ldc指令等具有引用常量池中可加载条目的操作数。有九种可加载条目(见表4.4-C),表示为以下形式的函子应用:
int(Value):CONSTANT_Integer_infofloat(Value):CONSTANT_Float_infolong(Value):CONSTANT_Long_infodouble(Value):CONSTANT_Double_infoclass(ClassName):CONSTANT_Class_infostring(Value):CONSTANT_String_infomethodHandle(Kind, Reference):CONSTANT_MethodHandle_infomethodType(MethodDescriptor):CONSTANT_MethodType_infodconstant(ConstantName, FieldDescriptor):CONSTANT_Dynamic_info
例如,加载int常量91的ldc指令表示为ldc(int(91))。
4.10.1.4 栈映射帧与类型转换
栈映射帧在Prolog中表示为术语列表:stackMap(Offset, TypeState),其中Offset是字节码偏移量,TypeState是该偏移量的预期传入类型状态。
类型状态是从局部变量和操作数栈位置到验证类型的映射,形式为frame(Locals, OperandStack, Flags)。
Locals是验证类型列表,Locals[i]表示局部变量i的类型。long和double占用两个局部变量,第一个为类型本身,第二个为top。OperandStack是验证类型列表,第一个元素表示栈顶,后续元素依次向下。long和double占用两个栈条目,第一个为top,第二个为类型本身。Flags是列表,如果任何局部变量具有类型uninitializedThis,则包含flagThisUninit,否则为空。
子类型化逐点扩展到类型状态。帧赋值兼容性检查需要栈长度相同。
frameIsAssignable(frame(Locals1, StackMap1, Flags1), frame(Locals2, StackMap2, Flags2)) :-
length(StackMap1, StackMapLength), length(StackMap2, StackMapLength),
maplist(isAssignable, Locals1, Locals2),
maplist(isAssignable, StackMap1, StackMap2),
subset(Flags1, Flags2).大多数单条指令的类型规则依赖于有效类型转换的概念。有效类型转换是指可以从传入类型状态的操作数栈中弹出预期类型列表,并用预期结果类型替换它们,产生新类型状态,且新栈长度不超过声明最大值。
validTypeTransition(Environment, ExpectedTypesOnStack, ResultType,
frame(Locals, InputOperandStack, Flags),
frame(Locals, NextOperandStack, Flags)) :-
popMatchingList(InputOperandStack, ExpectedTypesOnStack, InterimOperandStack),
pushOperandStack(InterimOperandStack, ResultType, NextOperandStack),
operandStackHasLegalLength(Environment, NextOperandStack).弹出列表和单个类型的谓词,以及压入类型的谓词,处理long/double占用两个槽位的情况。
popMatchingList(OperandStack, [], OperandStack).
popMatchingList(OperandStack, [P | Rest], NewOperandStack) :-
popMatchingType(OperandStack, P, TempOperandStack, _ActualType),
popMatchingList(TempOperandStack, Rest, NewOperandStack).
popMatchingType([ActualType | OperandStack], Type, OperandStack, ActualType) :-
sizeOf(Type, 1), isAssignable(ActualType, Type).
popMatchingType([top, ActualType | OperandStack], Type, OperandStack, ActualType) :-
sizeOf(Type, 2), isAssignable(ActualType, Type).
sizeOf(X, 2) :- isAssignable(X, twoWord).
sizeOf(X, 1) :- isAssignable(X, oneWord).
sizeOf(top, 1).
pushOperandStack(OperandStack, 'void', OperandStack).
pushOperandStack(OperandStack, Type, [Type | OperandStack]) :- sizeOf(Type, 1).
pushOperandStack(OperandStack, Type, [top, Type | OperandStack]) :- sizeOf(Type, 2).
operandStackHasLegalLength(Environment, OperandStack) :-
length(OperandStack, Length),
maxOperandStackLength(Environment, MaxStack),
Length =< MaxStack.dup指令等不基于子类型,而是基于类别类型。定义了popCategory1和popCategory2等谓词。
4.10.1.5 类型检查抽象方法和本地方法
抽象方法和本地方法如果在不覆盖final方法的情况下被认为是类型安全的。
methodIsTypeSafe(Class, Method) :-
doesNotOverrideFinalMethod(Class, Method),
methodAccessFlags(Method, AccessFlags),
member(abstract, AccessFlags).
methodIsTypeSafe(Class, Method) :-
doesNotOverrideFinalMethod(Class, Method),
methodAccessFlags(Method, AccessFlags),
member(native, AccessFlags).私有方法和静态方法与动态方法分派正交,因此它们不会覆盖其他方法(§5.4.5)。
doesNotOverrideFinalMethod(class('java/lang/Object', L), Method) :- isBootstrapLoader(L).
doesNotOverrideFinalMethod(Class, Method) :- isPrivate(Method, Class).
doesNotOverrideFinalMethod(Class, Method) :- isStatic(Method, Class).
doesNotOverrideFinalMethod(Class, Method) :-
isNotPrivate(Method, Class),
isNotStatic(Method, Class),
doesNotOverrideFinalMethodOfSuperclass(Class, Method).
doesNotOverrideFinalMethodOfSuperclass(Class, Method) :-
classSuperClassName(Class, SuperclassName),
classDefiningLoader(Class, L),
loadedClass(SuperclassName, L, Superclass),
classMethods(Superclass, SuperMethodList),
finalMethodNotOverridden(Method, Superclass, SuperMethodList).finalMethodNotOverridden谓词处理搜索超类方法,如果找到final方法且当前方法不能覆盖它(因为它是private或static,或不是final),则返回成功;否则递归到超类。
4.10.1.6 类型检查带代码的方法
非抽象、非本机方法如果是类型正确的,则具有代码且代码类型正确。
methodIsTypeSafe(Class, Method) :-
doesNotOverrideFinalMethod(Class, Method),
methodAccessFlags(Method, AccessFlags),
methodAttributes(Method, Attributes),
notMember(native, AccessFlags),
notMember(abstract, AccessFlags),
member(attribute('Code', _), Attributes),
methodWithCodeIsTypeSafe(Class, Method).方法带代码是类型安全的,如果可以将代码和栈映射帧合并成一个流,使每个栈映射帧位于其对应指令之前,并且合并后的流是类型正确的。方法的异常处理器(如果有)也必须是合法的。
methodWithCodeIsTypeSafe(Class, Method) :-
parseCodeAttribute(Class, Method, FrameSize, MaxStack,
ParsedCode, Handlers, StackMap),
mergeStackMapAndCode(StackMap, ParsedCode, MergedCode),
methodInitialStackFrame(Class, Method, FrameSize, StackFrame, ReturnType),
Environment = environment(Class, Method, ReturnType, MergedCode, MaxStack, Handlers),
handlersAreLegal(Environment),
mergedCodeIsTypeSafe(Environment, MergedCode, StackFrame).异常处理器表示为handler(Start, End, Target, ClassName)。处理器合法性的检查包括范围、目标存在、异常类为Throwable子类,以及对于<init>方法中的处理器,如果覆盖了invokespecial <init>,则处理器必须不能正常返回(必须抛出异常或永远循环)。
handlersAreLegal(Environment) :-
exceptionHandlers(Environment, Handlers),
checklist(handlerIsLegal(Environment), Handlers).
handlerIsLegal(Environment, Handler) :- ... // 详细规则见原文合并栈映射和代码的规则:
mergeStackMapAndCode([], CodeList, CodeList).
mergeStackMapAndCode([stackMap(Offset, Map) | RestMap],
[instruction(Offset, Parse) | RestCode],
[stackMap(Offset, Map), instruction(Offset, Parse) | RestMerge]) :-
mergeStackMapAndCode(RestMap, RestCode, RestMerge).
mergeStackMapAndCode([stackMap(OffsetM, Map) | RestMap],
[instruction(OffsetP, Parse) | RestCode],
[instruction(OffsetP, Parse) | RestMerge]) :-
OffsetP < OffsetM,
mergeStackMapAndCode([stackMap(OffsetM, Map) | RestMap], RestCode, RestMerge).方法的初始类型状态由methodInitialStackFrame计算,包括this类型和参数类型,以及标志。
methodInitialStackFrame(Class, Method, FrameSize, frame(Locals, [], Flags), ReturnType) :-
methodDescriptor(Method, Descriptor),
parseMethodDescriptor(Descriptor, RawArgs, ReturnType),
expandTypeList(RawArgs, Args),
methodInitialThisType(Class, Method, ThisList),
flags(ThisList, Flags),
append(ThisList, Args, ThisArgs),
expandToLength(ThisArgs, FrameSize, top, Locals).展开类型列表,将long/double替换为两个条目。
expandTypeList([], []).
expandTypeList([Item | List], [Item | Result]) :- sizeOf(Item, 1), expandTypeList(List, Result).
expandTypeList([Item | List], [Item, top | Result]) :- sizeOf(Item, 2), expandTypeList(List, Result).methodInitialThisType根据方法是静态还是实例,以及是否为<init>,决定this的类型。
合并后的流类型正确性检查:
mergedCodeIsTypeSafe(Environment, [stackMap(Offset, MapFrame) | MoreCode],
frame(Locals, OperandStack, Flags)) :-
frameIsAssignable(frame(Locals, OperandStack, Flags), MapFrame),
mergedCodeIsTypeSafe(Environment, MoreCode, MapFrame).
mergedCodeIsTypeSafe(Environment, [instruction(Offset, Parse) | MoreCode],
frame(Locals, OperandStack, Flags)) :-
instructionIsTypeSafe(Parse, Environment, Offset,
frame(Locals, OperandStack, Flags),
NextStackFrame, ExceptionStackFrame),
instructionSatisfiesHandlers(Environment, Offset, ExceptionStackFrame),
mergedCodeIsTypeSafe(Environment, MoreCode, NextStackFrame).
mergedCodeIsTypeSafe(Environment, [stackMap(Offset, MapFrame) | MoreCode], afterGoto) :-
mergedCodeIsTypeSafe(Environment, MoreCode, MapFrame).
mergedCodeIsTypeSafe(_Environment, [instruction(_, _) | _MoreCode], afterGoto) :-
write_ln('No stack frame after unconditional branch'), fail.
mergedCodeIsTypeSafe(_Environment, [endOfCode(Offset)], afterGoto).分支目标类型安全要求目标有对应的栈帧,且当前帧可赋值给它。
targetIsTypeSafe(Environment, StackFrame, Target) :-
offsetStackFrame(Environment, Target, Frame),
frameIsAssignable(StackFrame, Frame).指令满足异常处理器的要求:对于每个适用的处理器,处理器目标在传入异常类型状态(栈上只有一个异常对象)下是类型安全的。
4.10.1.7 类型检查加载和存储指令
所有加载指令遵循通用模式:从局部变量加载类型Type的值,要求局部变量类型为ActualType且ActualType可赋值给Type,然后将其压入栈。
loadIsTypeSafe(Environment, Index, Type, StackFrame, NextStackFrame) :-
StackFrame = frame(Locals, _OperandStack, _Flags),
nth0(Index, Locals, ActualType),
isAssignable(ActualType, Type),
validTypeTransition(Environment, [], ActualType, StackFrame, NextStackFrame).存储指令同样:从栈弹出与Type匹配的值,然后赋值给局部变量,并更新局部变量数组(处理long/double的占用)。
storeIsTypeSafe(_Environment, Index, Type, frame(Locals, OperandStack, Flags),
frame(NextLocals, NextOperandStack, Flags)) :-
popMatchingType(OperandStack, Type, NextOperandStack, ActualType),
modifyLocalVariable(Index, ActualType, Locals, NextLocals).modifyLocalVariable递归处理局部变量数组的修改,并处理覆盖相邻槽位的情况。
4.10.1.8 类型检查protected成员
访问protected成员需要额外的检查(JLS §6.6.2.1)。只适用于超类中的protected成员。规则包括:如果成员类不是超类,则忽略;如果超类中有protected成员在不同运行时包,则要求目标接收者类型可赋值给当前类。
详细的Prolog谓词passesProtectedCheck实现了这些逻辑。
4.10.1.9 类型检查指令(汇总)
本节为每条指令(约200条)提供了类型检查规则。每条规则都描述了传入类型状态的要求、正常完成后的类型状态和异常完成后的类型状态(通常异常完成时栈被清空)。由于篇幅,此处仅概括主要指令的规则:
aaload:弹出int和arrayref,要求数组组件类型为引用,推回组件类型。aastore:弹出value、index、arrayref,要求value为引用且可赋值给数组组件类型。aconst_null:推入null。aload:加载引用类型局部变量。anewarray:弹出count,推入新数组引用,组件类型由常量池条目指定。areturn:返回引用,要求与返回类型匹配。arraylength:弹出数组引用,推入int长度。astore:存储引用到局部变量。athrow:弹出Throwable子类实例并抛出。baload:从byte或boolean数组加载,转为int。bastore:存储int到byte或boolean数组。bipush:推入int常量。caload:从char数组加载,转为int。castore:存储int到char数组。checkcast:检查引用类型,不改变栈。- 转换指令如
d2f、d2i、d2l等,将一种数值类型转换为另一种。 - 算术指令如
dadd、fadd、iadd、ladd等,弹出两个操作数,推入结果。 - 比较指令如
dcmpg、dcmpl、fcmpg、fcmpl、lcmp,弹出两个值,推入int结果。 - 加载常量指令如
ldc、ldc_w、ldc2_w,从常量池推入值。 - 控制转移指令如
if、goto、tableswitch等。 - 方法调用指令如
invokevirtual、invokeinterface、invokespecial、invokestatic、invokedynamic,各有其参数匹配和接收者类型要求。 - 字段访问指令如
getfield、putfield、getstatic、putstatic。 - 同步指令
monitorenter、monitorexit。 - 数组创建指令
newarray、anewarray、multianewarray。 - 对象创建
new。 - 操作数栈管理指令如
pop、dup、swap等。
每条指令的详细Prolog规则在规范原文中有完整定义。
4.10.2 类型推断验证
不包含StackMapTable属性的class文件(版本号必为49.0或以下)必须使用类型推断进行验证。
4.10.2.1 类型推断验证过程
在链接期间,验证器通过对每个方法进行数据流分析来检查class文件中每个方法的Code属性的code数组。验证器确保在程序的任何给定点,无论采用什么代码路径到达该点,以下所有条件都成立:
- 操作数栈始终具有相同的大小并包含相同类型的值。
- 除非已知局部变量包含适当类型的值,否则不会访问它。
- 方法使用适当的参数调用。
- 字段仅使用适当类型的值赋值。
- 所有操作码在操作数栈和局部变量数组中都有适当类型的参数。
为效率起见,某些原则上可以由验证器执行的测试会延迟到方法的代码实际第一次被调用时进行。这样验证器避免了在不必需时加载类文件。
例如,如果一个方法调用另一个返回类A实例的方法,并且该实例仅赋值给同一类型的字段,则验证器不会费心检查类A是否实际存在。但是,如果它被赋值给类型B的字段,则必须加载A和B的定义以确保A是B的子类。
4.10.2.2 字节码验证器
每个方法的代码被独立验证。首先,组成代码的字节被分解为指令序列,并将每条指令开始的索引放入数组中。然后验证器第二次遍历代码并解析指令。在此过程中,构建一个数据结构来保存方法中每条Java虚拟机指令的信息。检查每条指令的操作数(如果有)以确保其有效。例如:
- 分支必须在方法的
code数组范围内。 - 所有控制流指令的目标都必须是指令的开始(
wide指令的目标可以是wide本身,但不能是被修改的操作码)。 - 没有指令可以访问或修改索引大于或等于其方法指示分配的局部变量数量的局部变量。
- 所有对常量池的引用必须是适当类型的条目。
- 代码不能在指令中间结束。
- 执行不能掉出代码末尾。
- 对于每个异常处理器,保护代码的起始点和结束点必须在指令的开始处(或结束点紧接代码末尾)。起始点必须在结束点之前。异常处理器代码必须从有效指令开始。
对于方法的每条指令,验证器记录在执行该指令之前操作数栈和局部变量数组的内容。对于操作数栈,需要知道栈高度和每个值的类型。对于每个局部变量,需要知道其内容的类型或它包含不可用/未知值。
接下来,初始化数据流分析器。对于方法的第一条指令,表示参数的局部变量初始包含方法类型描述符指示的类型的值;操作数栈为空。所有其他局部变量包含非法值。对于其他尚未检查的指令,没有关于操作数栈或局部变量的信息可用。
最后,运行数据流分析器。对于每条指令,一个“已更改”位指示是否需要查看该指令。最初,仅为第一条指令设置“已更改”位。数据流分析器执行以下循环:
- 选择“已更改”位被设置的Java虚拟机指令。如果没有指令的“已更改”位被设置,则方法已成功验证。否则,关闭所选指令的“已更改”位。
- 模拟指令对操作数栈和局部变量数组的影响:
- 如果指令使用操作数栈的值,确保栈上有足够数量的值,并且栈顶值具有适当类型。
- 如果指令使用局部变量,确保指定的局部变量包含适当类型的值。
- 如果指令将值压入操作数栈,确保操作数栈有足够空间容纳新值,并将指示的类型添加到栈顶。
- 如果指令修改局部变量,记录该局部变量现在包含新类型。
- 确定可以跟随当前指令的指令。后继指令可以是:下一条指令(如果当前指令不是无条件控制转移指令);条件或无条件分支的目标;该指令的任何异常处理器。
- 将当前指令执行结束时操作数栈和局部变量数组的状态合并到每个后继指令中:
- 如果是后继指令第一次被访问,记录步骤2中计算的操作数栈和局部变量值作为后继指令执行前的状态,并设置其“已更改”位。
- 如果后继指令之前见过,将步骤2中计算的值与已有的值合并,如果有任何修改则设置“已更改”位。
- 对于异常处理器,记录操作数栈上只有一个异常类型的对象,局部变量值来自步骤2之前的状态(而非步骤2的结果)。
- 继续步骤1。
合并操作数栈时,两个栈上的值数量必须相同。然后比较对应值并计算合并值:
- 如果一个是原始类型,另一个必须相同原始类型,合并结果为该类型。
- 如果一个是非数组引用类型,另一个必须是引用类型(数组或非数组),合并结果为两者的第一个公共超类型的引用。
- 如果两个都是数组引用类型,则根据维度处理。
合并局部变量数组状态类似,但如果对应原始类型不同,则合并为不可用值。
如果数据流分析器运行完成而没有报告验证失败,则方法已成功验证。
某些指令和数据类型使数据流分析复杂化,包括long/double的处理、实例初始化方法中的未初始化对象,以及finally子句中的jsr/ret(详见规范原文)。
4.11 Java虚拟机的限制
以下Java虚拟机的限制隐含在class文件格式中:
- 每个类或接口的常量池被
ClassFile结构的16位constant_pool_count字段限制为65535个条目(§4.1)。这限制了单个类或接口的总复杂度。 - 类或接口声明的字段数被
fields_count项限制为65535(§4.1)。注意不包括继承的字段。 - 类或接口声明的方法数被
methods_count项限制为65535(§4.1)。不包括继承的方法。 - 类或接口的直接超接口数量被
interfaces_count项限制为65535(§4.1)。 - 方法调用时创建的帧中局部变量数组的最大局部变量数被
Code属性的max_locals项(§4.7.3)以及16位局部变量索引限制为65535。long和double占用两个局部变量,因此减少此限制。 - 帧中操作数栈的大小被
Code属性的max_stack字段限制为65535个值(§4.7.3)。long和double贡献两个单位,减少限制。 - 方法参数的数量被方法描述符(§4.3.3)限制为255,其中包括实例方法调用中
this的一个单位。long和double贡献两个单位,减少限制。 - 字段和方法名称、描述符以及其他常量字符串值(包括
ConstantValue属性引用的)的长度被CONSTANT_Utf8_info结构的16位length项限制为65535个字符(§4.4.7)。注意限制的是编码字节数,不是字符数,多字节字符进一步约束。 - 数组的维度被
multianewarray指令的dimensions操作数以及multianewarray、anewarray和newarray指令的约束限制为255(§4.9.1,§4.9.2)。