类文件格式(上)

93 阅读1小时+

本章描述了 Java 虚拟机的类文件格式。每个类文件都包含单个类或接口的定义。尽管一个类或接口不一定需要在文件中实际包含其外部表示形式(例如,因为该类是由类加载器生成的),但我们将在口语中称任何有效的类或接口表示形式为处于类文件格式之中。

类文件由一系列 8 位字节组成。所有 16 位、32 位和 64 位的量都是通过依次读取两个、四个和八个连续的 8 位字节来构建的。多字节数据项总是以大端序存储,高位字节在前。在 Java SE 平台上,这种格式由接口 java.io.DataInput 和 java.io.DataOutput 以及类如 java.io.DataInputStream 和 java.io.DataOutputStream 支持。

本章定义了其自身表示类文件数据的数据类型集:类型 u1、u2 和 u4 分别表示无符号的一字节、两字节或四字节量。在 Java SE 平台上,这些类型可以通过接口中的方法如 readUnsignedByte、readUnsignedShort 和 readInt 来读取。java.io.DataInput。

本章使用类似 C 语言结构的伪结构来介绍类文件格式。为了避免与类的字段、类实例等混淆,描述类文件格式的结构内容被称为项。描述类文件格式的连续项依次存储在类文件中,没有填充或对齐。

在几个类文件结构中使用了由零个或多个可变大小的项组成的表。尽管我们使用类似于数组的语法来引用表项,但由于表是由大小不同的结构流组成的事实,这意味着无法直接将表索引转换为表中的字节偏移量。

当我们将数据结构称为数组时,它由零个或多个连续的固定大小的项组成,并可以像数组一样进行索引。

在本章中提及的 ASCII 字符应被理解为指代与该 ASCII 字符相对应的 Unicode 码点。

类文件结构

一个类文件由一个单一的 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

提供了标识类文件格式的魔法数字; 其值为 0xCAFEBABE 。

次要版本 minor_version,主要版本 major_version

minor_version 和 major_version 项的值分别是此类文件的次要版本号和主版本号。二者共同决定了类文件格式的版本。如果一个类文件的主版本号为 M,次要版本号为 m,那么我们将其类文件格式的版本表示为 M.m。因此,类文件格式的版本可以按字典顺序排列,例如 1.5 < 2.0 < 2.1。

如果一个 Java 虚拟机实现支持某个类文件格式的版本 v,那么只有当 v 落在某个连续的范围 Mi.0 ≤ v ≤ Mj.m 中时它才支持该类文件格式。Java SE 平台的发布级别决定了这个范围。

Oracle 的 Java 虚拟机实现(在 JDK 1.0.2 版本中)支持主版本号为 45.0 至 45.3 的类文件格式(包括这两个版本)。JDK 1.1.* 版本支持主版本号在 45.0 至 45.65535 范围内的类文件格式(包括这两个版本)。对于 k ≥ 2,JDK 1.k 版本支持主版本号在 45.0 至 44+k.0 范围内的类文件格式(包括这两个版本)。

常量池条目数 constant_pool_count

常量池计数项的值等于常量池表中的条目数量加一。如果常量池索引大于零且小于常量池计数项的值,则该常量池索引被视为有效,但长整型和双精度型常量的情况除外。

常量池 constant_pool[]

常量池是一个结构表,其中包含了各种字符串常量、类和接口名称、字段名称以及其他在 ClassFile 结构及其子结构中被引用的常量。每个常量池表项的格式由其第一个“标签”字节指示。

常量池表从索引 1 到 constant_pool_count - 1 进行编号。

访问标志 access_flags

access_flags 项的值是一个标志位的掩码,用于标识对这个类或接口的访问权限和属性。当某个标志位被设置时,其具体含义在下表中有所说明。

image.png 一个接口通过设置了 ACC_INTERFACE 标志来加以区分。如果未设置该标志,则此类文件定义的是一个类,而非接口。

ACC_INTERFACE 标志用于区分一个接口和一个类。如果未设置该标志,则此类文件定义的是一个类,而非接口。 类文件结构 类文件格式72

如果设置了 ACC_INTERFACE 标志,那么 ACC_ABSTRACT 标志也必须被设置,并且 ACC_FINAL、ACC_SUPER 和 ACC_ENUM 标志的设置不能同时存在。

如果未设置 ACC_INTERFACE 标志,则上表中的其他任何标志(除了 ACC_ANNOTATION)都可以被设置。然而,这样的类文件不能同时设置 ACC_FINAL 和 ACC_ABSTRACT 标志(JLS §8.1.1.2)。

ACC_SUPER 标志指示如果在该类或接口中出现 invokespecial 指令(§invokespecial),则要表达的两种替代语义中哪一种(§invokespecial)。编译器到 Java 虚拟机指令集应设置 ACC_SUPER 标志。在 Java SE 8 及更高版本中,Java 虚拟机认为每个类文件中都设置了 ACC_SUPER 标志,无论类文件中的标志实际值如何以及类文件的版本如何。

ACC_SUPER 标志的存在是为了与由较旧的 Java 编译器编译的代码保持向后兼容性。在 JDK 1.0.2 之前的版本中,编译器生成的 access_flags 中代表 ACC_SUPER 的标志没有指定的含义,而 Oracle 的 Java 虚拟机实现如果设置了该标志则会忽略它。ACC_SYNTHETIC 标志表示该类或接口是由编译器生成的,并且不会出现在源代码中。

注解类型必须设置 ACC_ANNOTATION 标志。如果设置了 ACC_ANNOTATION 标志,则还必须设置 ACC_INTERFACE 标志。

ACC_ENUM 标志表示该类或其超类被声明为枚举类型。

上表中未分配的所有 access_flags 项的位都预留用于将来使用。在生成的类文件中应将其设置为零,并且应被 Java 虚拟机实现忽略。

当前类索引 this_class

“this_class”项的值必须是常量池表中的一个有效索引。该索引处的常量池条目必须是一个表示由该类文件定义的类或接口的“CONSTANT_Class_info”结构的条目。

父类索引 super_class

对于一个类而言,super_class 项的值要么必须为零,要么必须是常量池表中的一个有效索引。如果 super_class 项的值不为零,那么该索引处的常量池条目必须是一个表示由本类文件定义的类的直接超类的 CONSTANT_Class_info 结构体。直接超类以及其任何子类都不应是该类文件的直接超类。

超类(superclass)可以在其 ClassFile 结构体的 access_flags 成员中设置 ACC_FINAL 标志。 如果 super_class 成员的值为零,则此类文件必须代表类 Object,这是唯一一个没有直接父类(superclass)的类或接口。

对于接口而言,super_class 成员的值必须始终是常量池(constant_pool)表中有效索引的值。该索引处的常量池条目必须是一个表示类 Object 的 CONSTANT_Class_info 结构体。

实现的接口数 interfaces_count

“interfaces_count”项的值给出了这个类或接口类型的直接超类的数量。

接口 interfaces[]

接口数组中的每个值都必须是常量池表中的有效索引。对于每个值 interfaces[i](其中 0 ≤ i < interfaces_count),在 interfaces[i] 的每个值处的常量池条目必须是一个表示直接超类接口(即此类或接口类型的直接超类)的 CONSTANT_Class_info 结构体,其顺序按照源代码中给出的从左到右的顺序排列。

字段计数 fields_coun

fields_count 项的值给出了字段表中字段信息结构的数量。字段信息结构代表了由该类或接口类型声明的所有字段(包括类变量和实例变量)。

字段 fields[]

字段表中的每个值都必须是一个字段信息结构(§4.5),它给出了该类或接口中某个字段的完整描述。字段表只包含由该类或接口声明的那些字段。它不包括代表从超类或超接口继承的字段的项。

方法计数 methods_count

methods_count 项的值给出了方法表中方法信息结构的数量。

方法 methods[]

方法表中的每个值都必须是一个方法信息结构,它给出了该类或接口中某个方法的完整描述。如果在访问标志项(access_flags)中既没有 ACC_NATIVE 标志也没有 ACC_ABSTRACT 标志被设置,则该方法表不包括代表从超类或超接口继承的方法的项。

注意:这段文本似乎与 Java 类文件格式有关,但其中包含了一些不完整的或不准确的信息。完整的 Java 类文件格式规范可能会对这些内容进行更准确的描述。如果您需要更准确的信息,请参考 Java 类文件格式的官方文档或规范。 方法信息结构中,还提供了实现该方法的 Java 虚拟机指令。

方法信息结构代表了该类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法(§2.9)以及任何类或接口初始化方法。方法表不包括代表从超类或超接口继承的方法的项目。

属性数量 attributes_count

attributes_count 项的值给出了此类的属性表中的属性数量。

属性 attributes[]

属性表中的每个值都必须是一个 attribute_info 结构。 本规范定义在 ClassFile 结构的属性表中出现的属性列表。

命名的内部形式

二进制类和接口名称

在类文件结构中出现的类名和接口名总是以一种称为二进制名的完全限定形式呈现。此类名总是以 CONSTANT_Utf8_info 结构的形式呈现,因此在没有进一步限制的情况下,可以从整个 Unicode 代码空间中获取这些名称。类名和接口名是从那些 CONSTANT_NameAndType_info 结构中引用的,这些结构的描述符中包含此类名,以及从所有 CONSTANT_Class_info 结构中引用。出于历史原因,类文件结构中出现的二进制名的语法与 JLS 第 13.1 节中记录的二进制名语法不同。在这种内部形式中,通常用于构成二进制名的标识符之间的 ASCII 点(.)被 ASCII 正斜杠(/)所取代。标识符本身必须是未限定的名称。

例如,类 Thread 的正常二进制名称是 java.lang.Thread。在类文件格式中描述符所采用的内部形式中,对类 Thread 名称的引用是通过使用一个表示字符串“java/lang/Thread”的 CONSTANT_Utf8_info 结构来实现的。

不合格的名字

方法名、字段名、局部变量名以及形参名都被存储为无限定名。无限定名必须包含至少一个 Unicode 码点,并且不能包含任何 ASCII 字符(即句点或分号或左方括号或正斜杠)。

方法名还受到进一步的限制,即除了特殊方法名 和 之外,它们不能包含 ASCII 字符 < or >(即左尖括号或右尖括号)。

描述符

描述符是表示字段或方法类型的字符串。描述符是 在类文件格式中使用修改过的UTF-8字符串表示,因此 可以从整个Unicode编码空间中提取(没有进一步限制)。

语法符号

描述符是通过一种语法来指定的。该语法是一组产生式,用于描述字符序列如何能形成各种类型的语法正确的描述符。语法的终结符以固定宽度字体显示,非终结符以斜体字显示。对非终结符的定义由定义该非终结符的名称开始,后面跟着一个冒号。随后的多行中接着是该非终结符的一个或多个替代定义。产生式右侧的语法符号{x}表示 x 可以出现零次或多次。

产生式右侧的短语“(其中之一)”表示接下来的行或几行中的每个终端符号都是一个替代定义。

字段描述符

字段描述符表示类、实例或局部变量的类型。

image.png

基本类型(BaseType)的字符、对象类型(ObjectType)的 L 和 ; 以及数组类型(ArrayType)的 [ 均为 ASCII 字符。

ClassName 表示以内部形式编码的二进制类或接口名称。

字段描述符作为类型的解释如表 4.3-A 所示。 表示字段描述符的

image.png 整型实例变量的字段描述符仅仅是 I。 Object 类型实例变量的字段描述符是 Ljava/lang/Object;。请注意,这里使用的是一级类名的内部形式。 二维数组类型 double[][] 的实例变量的字段描述符是 [[[D。

方法描述

方法描述符包含零个或多个参数描述符,这些描述符表示该方法所接收参数的类型,还包含一个返回描述符,该描述符表示该方法所返回值(若有)的类型。

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,但调用方法时除了预期的参数外,还会传递调用实例方法时 Java 虚拟机指令隐式传递的指向调用方法的对象的引用。方法描述符中不会反映这一点。

常量池

Java 虚拟机指令并不依赖于类、接口、类实例或数组的运行时布局。相反,指令会引用常量池表中的符号信息。

所有常量池表项都具有以下通用格式:

cp_info { 
    u1 tag;
    u1 info[]; 
}

常量池表中的每个条目都必须以一个 1 字节的标签开头,该标签指示 cp_info 条目的类型。info 数组的内容会因标签的值而异。有效的标签及其值列于表中。每个标签字节后面必须跟着两个或更多字节,用于提供关于特定常量的具体信息。附加信息的格式会因标签值而异。

image.png

CONSTANT_Class_info 结构体

CONSTANT_Class_info 结构体用于表示一个类或一个接口:

CONSTANT_Class_info {
 u1 tag;
 u2 name_index;
}

CONSTANT_Class_info 结构体用于表示一个类或一个接口:

tag

该标签项的值为 CONSTANT_Class(7)。

name_index

name_index 项的值必须是常量池表中的一个有效索引。在该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构,该结构表示一个以内部形式编码的有效二进制类或接口名称

因为数组是对象,所以操作码anewarray和multianewarray - 不是操作码 new -可以通过CONSTANT_Class_info引用数组“类” constant_pool表中的结构。对于这样的数组类,类的名称 是数组类型的描述符。

例如,表示二维数组类型int[][]的类名是[[I, Thread[]类型的类名是[Ljava/lang/Thread;。

数组类型描述符只有在表示255或更少维时才有效。

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)。

ONSTANT_Methodref_info 结构体的标签项的值为 CONSTANT_Methodref(10)。

CONSTANT_InterfaceMethodref_info 结构体的标签项具有值 CONSTANT_InterfaceMethodref(11)。

class_index

“class_index”项的值必须是常量池表中的有效索引。在该索引处的常量池条目必须是一个“CONSTANT_Class_info”结构,它代表一个类或接口类型,并且该类型包含作为成员的字段或方法。

CONSTANT_Methodref_info 结构的“class_index”项必须是类类型,而非接口类型。

CONSTANT_InterfaceMethodref_info 结构的“class_index”项必须是接口类型。

CONSTANT_Fieldref_info 结构的“class_index”项可以是类类型,也可以是接口类型。
name_and_type_index

“name_and_type_index”项的值必须是常量池表中有效的一个索引。在该索引处的常量池条目必须是一个“CONSTANT_NameAndType_info”结构。该常量池条目指示字段或方法的名称和描述符。

在“CONSTANT_Fieldref_info”中,所指示的描述符必须是字段描述符。否则,所指示的描述符必须是方法描述符。

如果“CONSTANT_Methodref_info”结构中方法的名称以“<”(\u003c)开头,则该名称必须是特殊的名称“”,表示实例初始化方法。此类方法的返回类型必须是 void。

CONSTANT_String_info结构

结构体CONSTANT_String_info用于表示 字符串类型:

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结构表示Unicode序列 代码指向要初始化的String对象

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 结构体中的 bytes 字段表示整型常量的值。该值的字节存储采用大端序(高位字节优先)格式。

CONSTANT_Float_info 结构体中的 bytes 字段表示 IEEE 754 浮点单精度格式中浮点常量的值。该单精度格式表示的字节存储采用大端序(高位字节优先)格式。

CONSTANT_Float_info 结构所代表的值的确定方式如下所述。首先,将该值的字节转换为整数常量位数。然后:

  • 如果 bits 的值为 0x7f800000,则浮点数的值将是正无穷大。
  • 如果 bits 的值为 0xff800000,则浮点数的值将是负无穷大。
  • 如果 bits 的值在 0x7f800001 到 0x7fffffff 这个范围内,或者在 0xff800001 到 0xffffffff 这个范围内,那么浮点数的值将是 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 · 2e-150 的计算结果。.

CONSTANT_Long_info 和 CONSTANT_Double_info

CONSTANT_Long_info 和 CONSTANT_Double_info 分别代表 8 字节的数值型常量(长整型和双精度型):

CONSTANT_Long_info {
 u1 tag;
 u4 high_bytes;
 u4 low_bytes;
}
CONSTANT_Double_info {
 u1 tag;
 u4 high_bytes;
 u4 low_bytes;
}

在类文件的常量池表中,所有 8 字节常量都会占用两个条目。如果在常量池表中索引为 n 的位置存放的是 CONSTANT_Long_info 或 CONSTANT_Double_info 结构体,则池中的下一个可用条目位于索引为 n + 2 的位置。常量池索引 n + 1 必须有效但被视为不可用。

这些结构的组成部分如下:

tag

CONSTANT_Long_info 结构的标签项的值为 CONSTANT_Long(5)。 CONSTANT_Double_info 结构的标签项的值为 CONSTANT_Double(6)。

high_bytes, low_bytes

CONSTANT_Long_info 结构体中的 unsigned high_bytes 和 low_bytes 两项共同表示该长整型常量的值。

((long) high_bytes << 32) + low_bytes

在每个 high_bytes 和 low_bytes 的字节中,其存储方式遵循大端序(高位字节在前)。

CONSTANT_Double_info 结构中的 high_bytes 和 low_bytes 项共同表示 IEEE 754 浮点数格式(§2.3.2)中的双精度值。每个项的字节均以大端序(高位字节在前)的方式存储。

CONSTANT_Double_info 结构所表示的值如下确定。将 high_bytes 和 low_bytes 项转换为长整型常量位,其值等于

((long) high_bytes << 32) + low_bytes

然后:

  • 如果 bits 的值为 0x7ff0000000000000L,那么双精度值将会是正无穷大。
  • 如果 bits 的值为 0xfff0000000000000L,那么双精度值将会是负无穷大。
  • 如果 bits 的值在 0x7ff0000000000001L 至 0x7fffffffffffffffL 的范围内,或者在 0xfff0000000000001L 至 0xffffffffffffffffL 的范围内,那么双精度值将会是 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 · 2e-1075 的双精度值。

CONSTANT_NameAndType_info 结构体

CONSTANT_NameAndType_info 结构用于表示一个字段或方法, 但不表明它属于哪个类或接口类型:

CONSTANT_NameAndType_info {
 u1 tag;
 u2 name_index;
 u2 descriptor_index;
}

CONSTANT_NameAndType_info 结构体的各个项如下所示:

tag

CONSTANT_NameAndType_info 结构体的标签项具有值 CONSTANT_NameAndType (12) 。

name_index

name_index 项的值必须是常量池表中的有效索引。在该索引处的常量池条目必须是一个表示特殊方法名 或者表示字段或方法的有效未限定名称的 CONSTANT_Utf8_info 结构。
descriptor_index
描述符索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示有效字段描述符或方法描述符的 CONSTANT_Utf8_info 结构。

CONSTANT_Utf8_info 结构体

CONSTANT_Utf8_info 结构体用于表示常量字符串值:

CONSTANT_Utf8_info {
 u1 tag;
 u2 length;
 u1 bytes[length];
}

CONSTANT_Utf8_info 结构体的各个项如下所示:

tag

CONSTANT_Utf8_info 结构体的标签项具有值 CONSTANT_Utf8(1)。

length

长度项的值给出了字节数组中的字节数(并非生成字符串的长度)。

bytes[]

该字节数组包含了字符串的字节。 任何字节的值都不能为(字节)0。 任何字节都不能处于(字节)0xf0 到(字节)0xff 的范围内。

字符串内容采用修改后的 UTF-8 编码方式。修改后的 UTF-8 字符串的编码方式是这样的:对于仅包含非空 ASCII 字符的代码点序列,每个代码点仅使用 1 个字节即可表示,但 Unicode 码空间中的所有代码点都能被表示。修改后的 UTF-8 字符串并非以空终止符结尾。其编码方式如下:

  • 在范围 '\u0001' 到 '\u007F' 内的代码点由一个字节来表示:

0 bits 6-0

字节中的 7 位数据给出了所表示的代码点的值。

  • 空代码点('\u0000')以及在 '\u0080' 到 '\u07FF' 范围内的代码点,分别由一对字节 x 和 y 来表示:

image.png 这两个字节代表的代码点的值为:
((x & 0x1f) << 6) + (y & 0x3f)

  • 在范围 '\u0800' 到 '\uFFFF' 内的代码点由三个字节 x、y 和 z 来表示:

image.png 这三个字节代表的代码点的值为:

((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f)

  • 字符码点高于 U+FFFF(即所谓的补码字符)的部分,是通过分别对它们的 UTF-16 表示中的两个补码代码单元进行编码来实现的。每个补码代码单元都由三个字节表示。这意味着补码字符由六个字节 u、v、w、x、y 和 z 来表示:

image.png 这六个字节代表的代码点的值为:

0x10000 + ((v & 0x0f) << 16) + ((w & 0x3f) << 10) + ((y & 0x0f) << 6) + (z & 0x3f)

多字节字符的字节在类文件中是以大端序(高位字节在前)的方式存储的。 这种格式与“标准”的 UTF-8 格式之间存在两个差异。

首先,空字符(char 类型的 0)是使用 2 字节格式而非 1 字节格式进行编码的,因此修改后的 UTF-8 字符串永远不会包含嵌入的空字符。其次, 只使用标准 UTF-8 编码的 1 字节、2 字节和 3 字节格式。Java 虚拟机不识别标准 UTF-8 的 4 字节格式;它采用自己的两倍三字节格式。

欲知有关标准 UTF-8 格式的更多信息,请参阅《Unicode 标准》第 6.0 版第 3.9 节“Unicode 编码形式”。

CONSTANT_MethodHandle_info 结构体

CONSTANT_MethodHandle_info 结构体用于表示方法句柄:

CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}

CONSTANT_MethodHandle_info结构中的项如下所示。

tag

CONSTANT_MethodHandle_info结构的tag项有这个值 CONSTANT_MethodHandle(15)。

reference_kind

“reference_kind”项的值必须在 1 到 9 的范围内。该值表示此方法句柄的种类,它决定了其字节码行为。

**reference_index **

reference_index的值必须是常量池表中的有效索引。该索引处的常量池条目必须如下所示:

  • 若 REF_getField、REF_getStatic、REF_putField 或 REF_putStatic 这些 REF_kind 项的值为 1、2、3 或 4,则在该索引位置的常量池条目必须是一个 CONSTANT_Fieldref_info 结构(参见 §4.4.2),该结构代表一个要创建方法柄的字段。
  • 若 REF_kind 项的值为 5(REF_invokeVirtual)或 8(REF_newInvokeSpecial),则在该索引位置的常量池条目必须是一个 CONSTANT_Methodref_info 结构(参见 §4.4.2),该结构代表一个要创建方法柄的类的方法或构造函数(参见 §2.9)。
  • 若 REF_kind 项的值为 6(REF_invokeStatic)或 7(REF_invokeSpecial),且类文件版本号小于 52.0,则在该索引位置的常量池条目必须是一个 CONSTANT_Methodref_info 结构,代表要创建方法柄的类的方法;若类文件版本号为 52.0 或更高,则该索引位置的常量池条目必须是一个 CONSTANT_Methodref_info 结构或一个 CONSTANT_InterfaceMethodref_info 结构,代表要创建方法柄的类或接口的方法。
  • 如果引用种类项的值为9 (REF_invokeInterface), 那么该索引处的常量池条目必须是一个 类文件格式 常量池 CONSTANT_InterfaceMethodref_info结构,表示要为其创建方法句柄的接口的方法。 如果引用种类项的值为5 (REF_invokeVirtual),6 (REF_invokeStatic),7 (REF_invokeSpecial),或9 (REF_invokeInterface), CONSTANT_Methodref_info结构或CONSTANT_InterfaceMethodref_info结构所表示的方法的名称不得为或。 如果值为8 (REF_newInvokeSpecial),CONSTANT_Methodref_info结构所表示的方法的名称必须是。

CONSTANT_MethodType_info 结构体

CONSTANT_MethodType_info 结构用于表示一种方法类型:

CONSTANT_MethodType_info {
 u1 tag;
 u2 descriptor_index;
}

CONSTANT_MethodType_info 结构中的各项如下:

tag

CONSTANT_MethodType_info 结构的标签项的值为 CONSTANT_MethodType(16)。

descriptor_index

“descriptor_index”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构,该结构表示一个方法描述符。

CONSTANT_InvokeDynamic_info 结构体

CONSTANT_InvokeDynamic_info 结构由调用动态方法指令(§invokedynamic)使用,用于指定启动方法、动态调用名称、调用的参数和返回类型,以及(可选地)一系列被称为静态参数的额外常量,这些参数传递给启动方法。

CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; }

tag

CONSTANT_InvokeDynamic_info 结构的标签项的值为 CONSTANT_InvokeDynamic(18)。

bootstrap_method_attr_index

bootstrap_method_attr_index 项的值必须是此类文件的 bootstrap 方法表中 bootstrap_methods 数组的有效索引。

name_and_type_index

name_and_type_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_NameAndType_info 结构,该结构表示方法名称和方法描述符。

字段

每个字段都由一个“字段信息”结构体来描述。 在一个类文件中,任何两个字段都不能具有相同的名称和描述符。 该结构体的格式如下:

ield_info {
 u2 access_flags;
 u2 name_index;
 u2 descriptor_index;
 u2 attributes_count;
 attribute_info attributes[attributes_count];
}

“field_info”结构体的各个项如下所示:

access_flags

“access_flags”项的值是一个标志位的掩码,用于表示对该字段的访问权限及其属性。每个标志位在被设置时的具体含义,可在表中找到。

image.png

类的字段可以设置上表中的任何一项标志。然而,每个 一个类的字段最多只能有一个 ACC_PUBLIC、ACC_PRIVATE 和 ACC_PROTECTED 标志被设置,并且不能同时设置 ACC_FINAL 和 ACC_VOLATILE 标志。

接口的字段必须设置 ACC_PUBLIC、ACC_STATIC 和 ACC_FINAL 标志;它们可以设置 ACC_SYNTHETIC 标志,并且不能设置上表中的其他任何标志。 ACC_SYNTHETIC 标志表示此字段是由编译器生成的,不会出现在源代码中。 ACC_ENUM 标志表示此字段用于存储枚举类型的一个元素。 上表中未分配的所有访问标志位都预留用于未来使用。在生成的类文件中应将它们设置为零,并且应被 Java 虚拟机实现忽略。

** name_index**

“name_index”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是第 4.6 节所述的格式。 CONSTANT_Utf8_info 结构用于表示一个有效的未限定名称,该名称用于标识一个字段。

descriptor_index

“descriptor_index”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构,该结构表示一个有效的字段描述符。

attributes_count

“attributes_count”项的值表示此字段的附加属性的数量。

attributes[]

属性表中的每个值都必须是一个 attribute_info 结构。 一个字段可以与任何数量的可选属性相关联。 本规范中定义为出现在字段_info 结构的属性表中的属性列表如表所示。 关于出现在字段_info 结构的属性表中的属性的规则见第 4.7 节。 关于字段_info 结构的属性表中非预定义属性的规则见第 4.7.1 节。

方法

每种方法,包括每个实例初始化方法以及类或接口初始化方法,均由一个 method_info 结构来描述。 在一个类文件中,任何两个方法都不能具有相同的名称和描述符。 该结构具有以下格式:

method_info {
 u2 access_flags;
 u2 name_index;
 u2 descriptor_index;
 u2 attributes_count;
 attribute_info attributes[attributes_count];
}

“method_info”结构中的各项内容如下:

access_flags

“access_flags”项的值是一个标志位的掩码,用于表示此方法的访问权限和属性。每个标志位的解释(当被设置时)在表 4.6-A 中有详细说明。

image.png

类的方法可以设置表 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 或 ACC_STRICT 中的任何标志。

每个实例初始化方法最多只能设置一个 ACC_PUBLIC、ACC_PRIVATE 和 ACC_PROTECTED 标志,并且还可以设置 ACC_VARARGS、ACC_STRICT 和 ACC_SYNTHETIC 标志,但不能设置表 4.6-A 中的其他任何标志。

类和接口初始化方法由 Java 虚拟机隐式调用。它们的 access_flags 项的值会被忽略,除了 ACC_STRICT 标志的设置。

ACC_BRIDGE 标志用于指示由 Java 编程语言的编译器生成的桥接方法。 ACC_VARARGS 标志表示此方法在源代码级别接受可变数量的参数。声明接受可变数量参数的方法必须使用 ACC_VARARGS 标志设置为 1 进行编译。 所有其他方法必须使用 ACC_VARARGS 标志设置为 0 进行编译。

ACC_SYNTHETIC 标志表示此方法是由编译器生成的,并且不会出现在源代码中,除非它是以下方法之一:在第 4.7.8 节中有提及。 表 4.6-A 中未指定的“访问标志”项的所有部分均预留用于未来用途。在生成的类文件中,应将这些部分设置为零,并且 Java 虚拟机实现应忽略它们。

name_index

“name_index”项的值必须是常量池表中的有效索引。在该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构,该结构表示的是特殊方法名“”或“”,或者是表示方法的有效未限定名称。

descriptor_index

“descriptor_index”项的值必须是常量池表中的有效索引。在该索引处的常量池条目必须是一个表示有效方法描述符的“CONSTANT_Utf8_info”结构(参见第 4.3.3 节)。 本规范的未来版本可能会要求,如果“ACC_VARARGS”标志设置在“access_flags”项中,则方法描述符的最后一个参数描述符必须是数组类型。

attributes_count

“attributes_count”项的值表示此方法的附加属性数量。

attributes[]

属性表中的每个值都必须是一个“attribute_info”结构。 一个方法可以有任意数量的与之相关的可选属性。 本规范中定义为出现在方法信息结构的“attributes”表中的属性列表如表 4.7-C 所示。 关于出现在方法信息结构的“attributes”表中的属性的规则见第 4.7 节。 关于方法信息结构的“attributes”表中非预定义属性的规则见第 4.7.1 节。

属性

属性在类文件格式的 ClassFile、field_info、method_info 和 Code_attribute 结构中均有应用。 所有属性都具有以下通用格式:

attribute_info {
 u2 attribute_name_index;
 u4 attribute_length;
 u1 info[attribute_length];
}

对于所有属性而言,属性名称索引必须是类的常量池中有效的 16 位无符号索引。位于属性名称索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构,用于表示该属性的名称。属性长度项的值表示后续信息的字节数长度。该长度不包括包含属性名称索引和属性长度项的最初 6 个字节。 该规范预定义了 23 个属性。它们被列出了三次,以便于导航:

  • 表 4.7-A 按照本章中各属性的序号进行排列。每个 该属性会附带其定义时所采用的类文件格式的首个版本,以及相应的 Java SE 平台版本。(对于旧的类文件版本,会使用 JDK 版本来代替 Java SE 平台版本)。
  • 表 4.7-B 按照每个属性定义时所采用的类文件格式的首个版本进行排序。
  • 表 4.7-C 按照每个属性在类文件中定义出现的位置进行排序。

在本规范所涉及的使用情境中,即在类文件结构中的属性表中(这些预定义属性即出现在此处),这些预定义属性的名称是被保留的。

关于属性表中预定义属性是否存在所设定的任何条件,均在描述该属性的章节中明确说明。若未指定任何条件,则该属性可在属性表中出现任意次数。表格。

这些预定义的属性根据其用途被分为三类:

  1. 对于 Java 虚拟机而言,正确解读类文件至关重要的五个属性是:
  • ConstantValue
  • Code
  • StackMapTable
  • Exceptions
  • BootstrapMethods

在版本为 V 的类文件中,如果实现能够识别版本为 V 的类文件(且 V 至少是该属性首次定义的版本),那么该实现必须能够识别并正确读取这些属性,并且这些属性必须出现在其定义的位置。2. 对于 Java SE 平台的类库而言,正确解读类文件的关键属性有以下 12 个:

  • InnerClasses
  • EnclosingMethod
  • Synthetic
  • Signature
  • RuntimeVisibleAnnotations
  • RuntimeInvisibleAnnotations
  • RuntimeVisibleParameterAnnotations
  • RuntimeInvisibleParameterAnnotations
  • RuntimeVisibleTypeAnnotations
  • RuntimeInvisibleTypeAnnotations
  • AnnotationDefault
  • MethodParameters

如果类库的实现能够识别版本 V 的类文件(且 V 至少是该属性首次定义的版本),并且该属性出现在其被定义的位置,则该类文件中的每个版本 V 的属性都必须被该类库的实现正确识别和读取。3. 以下六个属性对于 Java 虚拟机或 Java SE 平台的类库正确解析类文件并非至关重要,但对于工具而言却很有用:

  • SourceFile
  • SourceDebugExtension
  • LineNumberTable
  • LocalVariableTable
  • LocalVariableTypeTable
  • Deprecated

Java 虚拟机或 Java SE 平台的类库实现对这些属性的使用是可选的。实现可以使用这些属性所包含的信息,或者也可以选择默默地忽略这些属性。

image.png

image.png

image.png

定义和命名新属性

编译器可以定义并生成包含新属性的类文件,这些类文件位于类文件结构、字段信息结构、方法信息结构以及代码属性(第 4.7.3 节)的属性表中。Java 虚拟机实现可以识别并使用这些属性表中发现的新属性。然而,任何未作为类文件规范的一部分定义的属性都不得影响类文件的语义。Java 虚拟机实现必须默默地忽略它们不识别的属性。例如,定义一个新属性以支持特定供应商的调试是被允许的。由于 Java 虚拟机实现必须忽略它们不识别的属性,因此为特定 Java 虚拟机实现编写的类文件即使其他实现无法利用其中包含的额外调试信息,也能被其他实现使用。

Java 虚拟机实现被明确禁止仅仅因为某些新属性的存在而抛出异常或拒绝使用类文件。当然,如果运行类文件的工具所使用的类文件中没有包含它们所需的全部属性,那么这些工具就可能无法正确运行。

两个原本打算作为不同属性来使用的属性,如果恰好使用了相同的属性名称并且长度也相同,那么在那些能够识别这两种属性的实现中就会产生冲突。根据《Java 语言规范》(Java SE 8 版)(JLS 第 6.1 节)中描述的包命名规则,除了本规范中定义的属性之外,其他定义的属性必须使用特定的名称。 本规范的未来版本可能会定义更多的属性。

ConstantValue属性

“ConstantValue”属性是字段信息结构的属性表中的一种固定长度属性。常量值属性表示一个常量表达式的值(JLS 第 15.28 节),其使用方式如下:

  • 如果字段信息结构中的“访问标志”项中的 ACC_STATIC 标志被设置,则由该字段信息结构所代表的字段将根据其常量值属性所表示的值进行初始化,作为声明该字段的类或接口的初始化的一部分。这发生在调用该类或接口的初始化方法之前。
  • 否则,Java 虚拟机必须默默地忽略该属性。 一个字段信息结构的属性表中最多只能有一个常量值属性。

“ConstantValue”属性的格式如下:

ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}

“ConstantValue_attribute”结构的各个项如下所示:

attribute_name_index

属性名称索引项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个表示字符串“ConstantValue”的 CONSTANT_Utf8_info 结构。

attribute_length

ConstantValue_attribute 结构的属性长度项的值必须为 2。

constantvalue_index

常量值索引项的值必须是常量池表中的有效索引。该索引处的常量池条目给出了此属性所表示的常量值。该常量池条目必须是与字段类型相匹配的适当类型,如表 4.7.2-A 所示。

image.png

Code属性

“代码”属性是方法信息结构的属性表中的一种可变长度属性。代码属性包含了方法的 Java 虚拟机指令以及辅助信息,包括实例初始化方法或类或接口初始化方法)。

如果该方法是原生方法或抽象方法,则其方法信息结构的属性表中不能包含“code”属性。否则,其方法信息结构的属性表中必须恰好包含一个“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];
}

代码属性结构中的各项如下:

attribute_name_index

属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“代码”的 CONSTANT_Utf8_info 结构(第 4.4.7 节)。

attribute_length

属性长度项的值表示该属性的长度(不包括开头的六字节)。

max_stack

最大栈深度项的值给出此方法在执行过程中任何时刻操作数栈的最大深度。

max_locals

最大局部变量数项的值给出在调用此方法时分配的局部变量数组中的局部变量数量,包括用于在方法调用时传递参数的局部变量。 对于长型或双精度型的值,最大的局部变量索引为 max_locals - 2 。对于任何其他类型的值,最大的局部变量索引为 max_locals - 1 。

code_length

code_length 项的值给出了此方法的代码数组中的字节数。 code_length 的值必须大于零(因为代码数组不能为空),且小于 65536 。

code[]

代码数组给出了实现该方法的 Java 虚拟机代码的实际字节。 当在字节可寻址的机器上将代码数组读入内存时,如果数组的第一个字节对 4 字节边界对齐,表切换和查找切换的 32 位偏移量将对齐为 4 字节。(有关这些指令的更多说明,请参阅有关代码数组对齐的描述。) 代码数组内容的具体约束条件非常广泛,并在单独的章节(§4.9)中给出。

exception_table_length

exception_table_length 项的值给出了异常表表中的条目数量。

exception_table[]

异常表数组中的每个条目描述了代码数组中的一个异常处理程序。异常表数组中处理程序的顺序是重要的。

每个异常表条目包含以下四个项目:

start_pc, end_pc

这两个项目的值(start_pc 和 end_pc)表示异常处理程序在代码数组中的有效活动范围。start_pc 的值必须是代码数组中指令操作码的有效索引。end_pc 的值要么必须是代码数组中指令操作码的有效索引,要么必须等于代码数组的长度(code_length)。start_pc 的值必须小于 end_pc 的值。

起始程序计数器(start_pc)是包含范围的,而结束程序计数器(end_pc)是不包含范围的;也就是说,异常处理程序必须在程序计数器处于 [起始程序计数器,结束程序计数器) 这个区间内时处于激活状态。

结束程序计数器(end_pc)为不包含范围这一事实是 Java 虚拟机设计中的一个历史错误:如果一个方法的 Java 虚拟机代码恰好长 65535 字节,并且以一个 1 字节长的指令结尾,那么该指令就无法被异常处理程序保护。编译器编写者可以通过将任何方法、实例初始化方法或静态初始化器(任何代码数组的大小)的生成的 Java 虚拟机代码的最大大小限制为 65534 字节来解决这个错误。

handler_pc

handler_pc 项的值表示异常处理程序的起始位置。该值必须是代码数组中的有效索引,并且必须是指令操作码的索引。

catch_type

如果 catch_type 项的值非零,则它必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Class_info 结构(§4.4.1),表示此异常处理程序要捕获的异常类。异常只有当抛出的异常是给定类的实例或者是其子类时,才会调用该处理程序。 验证器会检查该类是否为 Throwable 类或其子类(参见第 4.9.2 节)。 如果 catch_type 项的值为零,则此异常处理程序将适用于所有异常。 这用于实现 finally

attributes_count

“attributes_count”项的值表示“代码属性”的属性数量。

attributes[]

“attributes”表中的每个值都必须是一个“属性信息”结构。 一个“代码”属性可以与任意数量的可选属性相关联。 本规范中定义为出现在“代码属性”的“属性”表中的属性列表如表 4.7-C 所示。 关于“代码属性”表中出现的属性的规则见第 4.7 节。 关于“代码属性”表中非预定义属性的规则见第 4.7.1 节。

StackMapTable属性

“StackMapTable”属性是code属性的属性表中的一个可变长度属性。在验证过程中,通过类型检查会使用到“StackMapTable”属性。

在代码属性的属性表中,最多只能有一个“StackMapTable”属性。

在版本号为 50.0 及以上的类文件中,如果某个方法的“Code”属性没有“StackMapTable”属性,那么它就有一个隐式的栈映射属性(参见第 4.10.1 节)。这个隐式的栈映射属性等同于一个“StackMapTable”属性,其“entry_number”(条目数量)值为零。

“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 结构(第 4.4.7 节)。

attribute_length

属性长度项的值表示该属性的长度(不包括最初的六字节)。

number_of_entries

“条目数量”项的值给出了条目表中栈映射帧条目的数量。

entries[]

entries 表中的每一项都描述了该方法的一个栈映射帧。 entries 表中栈映射帧的排列顺序具有重要意义。

栈映射帧会明确(或隐含)指定其生效的字节码偏移量,以及该偏移量处局部变量和操作数栈条目的验证类型。

一个栈映射框架指定(无论是显式还是隐式)其适用的字节码偏移量,以及该偏移量处局部变量和操作数栈条目的验证类型。 条目表中描述的每个栈映射框架都依赖于前一个框架的部分语义。

方法的第一个栈映射框架是隐式的,由类型检查器根据方法描述符计算得出。因此,entries[0] 中的 stack_map_frame 结构描述了方法的第二个栈映射框架。

栈映射框架适用的字节码偏移量通过取框架中指定的值 offset_delta(无论是显式还是隐式),并将 offset_delta + 1 添加到前一个框架的字节码偏移量来计算,除非前一个框架是方法的初始框架。在这种情况下,栈映射框架适用的字节码偏移量是框架中指定的 offset_delta 值。

通过使用偏移量差值而非存储实际的字节码偏移量,我们从定义上确保了栈映射帧处于正确的排序顺序中。此外,通过始终使用公式“偏移量差值 + 1”来处理所有显式帧(而不是隐式的第一个帧),我们保证了不会出现重复项。

我们称字节码中的某条指令若满足以下条件,则与一个对应的栈映射帧相关联: 该指令起始于代码数组(Code 属性的数组)中索引为 i 的位置,并且 Code 属性具有一个名为 StackMapTable 的属性,其条目数组中包含在字节码索引为 i 处适用的栈映射帧。

验证类型指定了一个或两个位置的类型,其中位置要么是一个单独的局部变量,要么是一个单独的操作栈条目。验证类型由一个区分型联合体(verification_type_info)表示,该联合体由一个字节的标签组成,用于指示联合体中的哪个项正在使用,随后是零个或多个字节,用于提供关于该标签的更多信息。

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;
}
  • Top_variable_info 项表示本地变量具有顶级验证类型。 Top_variable_info { u1 tag = ITEM_Top; /* 0 */ }
  • Integer_variable_info 项表示该位置具有 int 验证类型。 Integer_variable_info { u1 tag = ITEM_Integer; /* 1 */ }
  • Float_variable_info 项表示该位置具有 float 验证类型。 Float_variable_info { u1 tag = ITEM_Float; /* 2 */ }
  • Null_variable_info 类型表示该位置具有 null 验证类型。 Null_variable_info { u1 tag = ITEM_Null; /* 5 */ }
  • UninitializedThis_variable_info 项表示该位置具有 uninitializedThis 验证类型。 UninitializedThis_variable_info { u1 tag = ITEM_UninitializedThis; /* 6 */ }
  • Object_variable_info 项表示该位置的验证类型是类,该类由位于 constant_pool 表中索引为 cpool_index 处的 CONSTANT_Class_info 结构 (§4.4.1) 表示。 Object_variable_info { u1 tag = ITEM_Object; /* 7 */ u2 cpool_index; }
  • Uninitialized_variable_info 项表示该位置的验证类型为 uninitialized(Offset)。Offset 项表示在包含此 StackMapTable 属性的 Code 属性的代码数组中,创建存储在该位置的对象的 new 指令 (§new) 的偏移量。 Uninitialized_variable_info { u1 tag = ITEM_Uninitialized; /* 8 */ u2 offset; }

一种指定本地变量数组中或操作数栈中两个位置的验证类型由以下验证_type_info联合体表示:

  • Long_variable_info项表示两个位置中的第一个具有long验证类型。 Long_variable_info { u1 tag = ITEM_Long; /* 4 */ }

  • Double_variable_info项表示两个位置中的第一个具有double验证类型。 Double_variable_info { u1 tag = ITEM_Double; /* 3 */ }

  • Long_variable_info 和 Double_variable_info 项指示两个位置中第二个位置的验证类型,如下所示:
    – 如果两个位置中的第一个是局部变量,则:
    › 它不能是索引最高的局部变量。
    › 编号更高的下一个局部变量的验证类型为 top。
    – 如果两个位置中的第一个是操作数栈条目,则:
    › 它不能是操作数栈的最顶端位置。
    › 靠近操作数栈顶部的下一个位置的验证类型为 top。
    › 操作数栈顶部的下一个位置具有验证类型 top

堆栈映射帧由一个区分联合体表示,即 stack_map_frame,它由一个字节的标签组成,该标签指示正在使用联合体的哪个项,随后是零个或多个字节,提供有关该标签的更多信息。

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_frame { u1 frame_type = SAME; /* 0-63 */ }
  • 帧类型 same_locals_1_stack_item_frame 由范围在 [64, 127] 的标签表示。此帧类型表示该帧与前一个帧具有完全相同的局部变量,并且操作数栈有一个条目。该帧的 offset_delta 值由公式 frame_type - 64 给出。单个栈条目的验证类型出现在帧类型之后。
same_locals_1_stack_item_frame {
 u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */ 
 verification_type_info stack[1];
}
  • 标签范围在[128-246]之间保留以供将来使用。
  • 框架类型 same_locals_1_stack_item_frame_extended 由标签 247 表示。此框架类型表示该框架具有与上一个框架完全相同的局部变量,并且操作数栈有一个条目。该框架的 offset_delta 值显式给出,这与框架类型 same_locals_1_stack_item_frame 不同。唯一的栈条目的验证类型出现在 offset_delta 之后。
same_locals_1_stack_item_frame_extended {
 u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED; /* 247 */
 u2 offset_delta;
 verification_type_info stack[1];
}

“框架类型‘chop_frame’由范围在[248 - 250]内的标签来表示。”这 帧类型表示该帧与前一个帧具有相同的局部变量,但最后的 k 个局部变量已缺失,并且操作数栈为空。k 的值由公式 251 - 帧类型给出。最终的译文:这个 该帧的偏移量差值值是明确给出的。

chop_frame {
 u1 frame_type = CHOP; /* 248-250 */
 u2 offset_delta;
}

假设前一帧中局部变量的验证类型由“locals”给出,其结构与完整帧类型中的数组结构相同。如果 在上一个帧中,locals[M-1] 代表局部变量 X,而 locals[M] 代表局部变量 Y,那么删除一个局部变量的效果就是,在新的帧中,locals[M-1] 将代表局部变量 X,而 locals[M] 则代表局部变量 Y。未定义。

如果 k 大于当前帧中局部变量的数量(即新帧中的局部变量数量会小于零),则这是个错误。

  • 类型为 same_frame_extended 的帧由标签 251 表示。这种帧类型表示该帧与前一帧具有完全相同的局部变量,并且操作数栈为空。与 same_frame 类型不同,此帧的偏移量差值值是明确给出的。
same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
u2 offset_delta;}

“帧类型 append_frame”由范围在 [252 - 254] 之间的标签来表示。这 帧类型表示该帧与前一帧具有相同的局部变量,但额外定义了 k 个局部变量,并且操作数栈为空。 k 的值由公式 frame_type - 251 给出。帧的偏移量差值值是明确给出的。

append_frame {
 u1 frame_type = APPEND; /* 252-254 */
 u2 offset_delta;
 verification_type_info locals[frame_type - 251];
}

locals 数组中的第 0 个元素表示第一个附加局部变量的验证类型。如果 locals[M] 表示局部变量 N,那么:
-如果 locals[M] 是 Top_variable_info、Integer_variable_info、Float_variable_info、Null_variable_info、UninitializedThis_variable_info、Object_variable_info 或 Uninitialized_variable_info 中的某一种,则 locals[M+1] 表示局部变量 N+1;并且
-locals[M+1] 表示如果 locals[M] 是 Long_variable_info 或 Double_variable_info 类型,则代表局部变量 N+2。 如果对于任何索引 i,locals[i] 表示的局部变量索引大于该方法的最大局部变量数量,则这是错误的。

  • 帧类型 full_frame 由标签 255 表示。帧的偏移量_delta 值是明确给出的。
full_frame {
 u1 frame_type = FULL_FRAME; /* 255 */
 u2 offset_delta;
 u2 number_of_locals;
 verification_type_info locals[number_of_locals];
 u2 number_of_stack_items;
 verification_type_info stack[number_of_stack_items];
}

“locals”字典中的第 0 个条目代表了本地变量 0 的验证类型。如果 locals[M] 表示局部变量 N,那么:

  1. 如果 locals[M] 是 Top_variable_info、Integer_variable_info、Float_variable_info、Null_variable_info、UninitializedThis_variable_info、Object_variable_info 或 Uninitialized_variable_info 中的一种,那么 locals[M+1] 就表示局部变量 N+1;并且
  2. locals[M+1] 表示如果 locals[M] 为 Long_variable_info 或 Double_variable_info 时,就是本地变量 N+2。

如果对于任何索引 i,locals[i] 表示的都是方法中本地变量索引值大于其最大值的本地变量,则这是错误的。 栈中的第 0 个条目表示操作数栈底部的验证类型,而栈中的后续条目则表示类文件格式属性 4.7 中的验证类型。113 对于位于操作数栈顶部附近的栈项,我们将其称为操作数栈的底部项。我们将操作数栈的底部称为栈项 0,而操作数栈的后续项则称为栈项 1、2 等。如果 stack[M] 表示栈项 N,那么:

  1. 如果 stack[M] 是 Top_variable_info、Integer_variable_info、Float_variable_info、Null_variable_info、UninitializedThis_variable_info、Object_variable_info 或 Uninitialized_variable_info 中的某一项,那么 stack[M+1] 就表示栈项 N+1;并且

  2. stack[M+1] 表示若 stack[M] 为 Long_variable_info 或 Double_variable_info 类型,则代表 stack 中的第 N+2 个条目。

若对于任何索引 i,stack[i] 所代表的栈条目的索引大于该方法的最大操作数栈大小,则此为错误情况。

Exceptions 属性

“异常”属性是方法信息结构的属性表中的一个可变长度属性。该属性表明一个方法可能抛出哪些已检查的异常。

在方法信息结构的属性表中,最多只能有一个“异常”属性。

“异常”属性的格式如下:

Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}

“Exceptions_attribute”结构中的各项如下:

attribute_name_index

“attribute_name_index”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是表示字符串“Exceptions”的CONSTANT_Utf8_info 结构。

attribute_length

“attribute_length”项的值表示属性长度(不包括开头的六字节)。

number_of_exceptions

“number_of_exceptions”项的值表示异常索引表中的条目数量。

exception_index_table[]

exception_index_table 数组中的每个值都必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示此方法声明要抛出的类类型的CONSTANT_Class_info 结构。 一个方法仅在满足以下三个条件中的至少一个时才应抛出异常:

  1. 异常是 RuntimeException 的实例或其子类的实例。
  2. 异常是 Error 的实例或其子类的实例。
  3. 异常是指定的异常类之一的实例(在上述指定中)。如上文所述的“异常索引表”,或者是其子类。
    这些要求在 Java 虚拟机中并不被强制执行;它们仅在编译时才被执行。

InnerClasses 属性

“InnerClasses”属性是类文件结构的属性表中的一种可变长度属性。

如果一个类或接口 C 的常量池中至少包含一个“CONSTANT_Class_info”条目,该条目表示的是一非包成员的类或接口,那么类文件结构中对于该类 C 必须存在一个确切的“InnerClasses”属性。

“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];
}

“InnerClasses_attribute”结构中的各项如下:

attribute_name_index

属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是表示字符串“InnerClasses”的 CONSTANT_Utf8_info 结构(第 4.4.7 节)。

attribute_length

属性长度项的值表示该属性的长度(不包括最初的六字节)。

number_of_classes

类数量项的值表示 classes 数组中的条目数量。

classes[]

常量池表中的每个 CONSTANT_Class_info 元素(它代表一个类或接口 C,且并非包成员)都必须在 classes 数组中有一个对应的条目。

如果一个类或接口具有成员(这些成员也是类或接口),那么其常量池表(以及因此其 InnerClasses 属性)必须引用每个这样的成员(JLS 第 13.1 节),即使该成员在该类中没有其他提及。

此外,每个嵌套类和嵌套接口的常量池表都必须引用其外部类,因此总体而言,每个嵌套类和嵌套接口都将拥有每个外部类以及其自身所有嵌套类和接口的 InnerClasses 信息。 classes 数组中的每个条目包含以下四项:

  1. inner_class_info_index

    “内部类信息索引”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示 C 的 CONSTANT_Class_info 结构。classes 数组项中的其余内容则提供了关于 C 的信息。

  2. outer_class_info_index 如果 C 不是类或接口的成员(也就是说,如果 C 是顶级类或接口(JLS 第 7.6 节)、局部类(JLS 第 14.3 节)或匿名类(JLS 第 15.9.5 节)),则“外部类信息索引”项的值必须为零。

    否则,该“外部类信息索引”项的值必须是常量池表中的有效索引,并且该索引处的条目必须为 4。7 特性 类文件格式116 一个 CONSTANT_Class_info 结构体,用于表示 C 是其成员的类或接口。

  3. inner_name_index 如果 C 是匿名的(JLS 第 15.9.5 节),inner_name_index 项的值必须为零。 否则,inner_name_index 项的值必须是常量池表中的一个有效索引,并且该索引处的条目必须是一个 CONSTANT_Utf8_info 结构体,它代表了 C 在编译此类文件时的原始简单名称。

  4. inner_class_access_flags inner_class_access_flags 项的值是一个用于表示编译此类文件时源代码中声明的类或接口 C 的访问权限和属性的标志的掩码。它是 由编译器用于在源代码不可用时恢复原始信息。这些标志在表 4.7.6-A 中有所规定。

image.png

表 4.7.6-A 中未赋值的所有 inner_class_access_flags 项均预留用于未来使用。在生成的类文件中应将它们设置为零,并且 Java 虚拟机应忽略这些值。实施方法。

如果一个类文件的版本号为 51.0 或更高,并且在其属性表中存在“InnerClasses”属性,那么对于“InnerClasses”属性中“classes”数组中的所有条目,如果“inner_name_index”项的值为零,则“outer_class_info_index”项的值必须为零。

Oracle 的 Java 虚拟机实现不会检查“InnerClasses”属性与该属性所引用的类或接口的类文件之间的一致性。

EnclosingMethod 属性

“EnclosingMethod”属性是“类文件结构”的“属性表”中的一种固定长度属性。如果一个类代表的是局部类或匿名类(JLS 第 14.3 节、JLS 第 15.9.5 节),那么该类就必须具有“EnclosingMethod”属性。

在“类文件结构”的“属性表”中,最多只能有一个“EnclosingMethod”属性。

“EnclosingMethod”属性的格式如下:

EnclosingMethod_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u2 class_index;
 u2 method_index;
}

attribute_name_index

属性名称索引项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构(第 4.4.7 节),表示字符串“EnclosingMethod”。

attribute_length

属性长度项的值必须为 4。

class_index

类索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Class_info 结构(第 4.4.1 节),表示包含当前类声明的最内层的类。

method_index

如果当前类并非立即被某个方法或构造函数所包围, 那么“方法索引”项的值必须为零。

特别地,如果当前类在源代码中立即被实例初始化器、静态初始化器、实例变量初始化器或类变量初始化器所包围, 那么“方法索引”必须为零。(前两种情况涉及本地类和匿名类,而后两种情况涉及在字段赋值右侧声明的匿名类。)

否则,“方法索引”项的值必须是常量池表中有效索引的值。该索引处的常量池条目必须是一个 CONSTANT_NameAndType_info 结构(§4.4.6),用于表示上述类引用中方法的名称和类型。

Java 编译器有责任确保通过“方法索引”所标识的方法确实是包含此“包围方法”属性的类的最内层的词法包围方法。

Synthetic 属性

“Synthetic”是类文件、字段信息或方法信息结构的属性表中的一种固定长度属性。 在源代码中未出现的成员必须使用“合成”属性进行标记,或者必须设置其“ACC_SYNTHETIC”标志。此要求的唯一例外情况是编译器生成的方法,这些方法不被视为实现的构件,即表示 Java 编程语言默认构造函数的实例初始化方法、类初始化方法以及 Enum.values() 和 Enum.valueOf() 方法。

“Synthetic”属性是在 JDK 1.1 中引入的,用于支持嵌套类和接口。

“Synthetic”的格式如下:

Synthetic_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
}

“Synthetic”结构中的各项内容如下:

attribute_name_index

属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“合成”的 CONSTANT_Utf8_info 结构,即“合成”字符串。

attribute_length

属性长度项的值必须为零。

Signature 属性

“Signature”属性是类文件、字段信息或方法信息结构的属性表中的一种固定长度属性。A “Signature”属性记录了一个类、接口、构造函数、方法或字段的签名,这些元素在 Java 编程语言中的声明使用了类型变量或参数化类型。有关这些类型的详细信息,请参阅《Java 语言规范》(Java SE 8 版)。

“Signature”属性的格式如下:

Signature_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u2 signature_index;
}

“Signature”结构中的各项内容如下:

attribute_name_index

“属性名称索引”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“签名”的 CONSTANT_Utf8_info 结构(第 4.4.7 节),即“签名”字符串。

attribute_length

“签名属性”结构的“属性长度”项的值必须为 2。

signature_index

“签名索引”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示类签名的 CONSTANT_Utf8_info 结构(如果此“签名属性”是类文件结构的属性);是一个表示方法签名的 CONSTANT_Utf8_info 结构(如果此“签名属性”是方法信息结构的属性);否则是一个表示字段签名的 CONSTANT_Utf8_info 结构。

Oracle 的 Java 虚拟机实现在类加载或链接过程中不会检查签名属性的格式正确性。相反,签名属性的检查是由 Java SE 平台类库中的方法来完成的,这些方法会展示类、接口、构造函数、方法和字段的通用签名。 例如,Class 类中的 getGenericSuperclass 方法和 java.lang.reflect.Executable 类中的 toGenericString 方法。

Signatures

签名是对用 Java 编程语言编写的声明进行编码,这些声明使用了 Java 虚拟机类型系统之外的类型。它们支持反射和调试功能,而且在仅具备类文件的情况下也能支持编译工作。

任何具有类型变量或参数化类型的声明(包括类、接口、构造函数、方法或字段)的 Java 编译器都必须为其生成一个签名。具体而言, Java 编译器必须生成以下内容:

  • 对于任何具有泛型特征、或其超类或超接口(或两者皆有)的参数化类型声明的类或接口,必须生成相应的类签名。
  • 对于任何具有泛型特征、或返回类型或形式参数类型为类型变量或参数化类型的(方法或构造函数)声明,或在抛出语句中存在类型变量,或包含任何组合形式的任何方法或构造函数声明,都必须生成相应的方法签名。“其中的” 如果方法或构造函数声明中的“抛出”子句不涉及类型变量,那么编译器可能会将该声明视为没有“抛出”子句,以便生成方法签名。
  • 对于任何字段、形式参数或局部变量声明,如果其类型使用了类型变量或参数化类型,则为其指定签名。 签名是使用遵循§4.3.1 中的符号表示法的语法来指定的。在 此外还有以下说明:
  • 在一个产生式右侧的符号[x] 表示 x 可能出现 0 次或 1 次。也就是说,x 是一个可选符号。包含这个可选符号的另一种情况实际上定义了两种情况:一种不包含该可选符号,一种包含它。
  • 如果右侧很长,可以在第二行继续书写,只需将第二行清晰地缩进即可。

该语法包含一个终结符“标识符”,用于表示类型、字段、方法、形式参数、局部变量或类型变量的名称,这些名称是由 Java 编译器生成的。此类名称不得包含任何 ASCII 字符。;[ / < > : (即方法名中禁止使用的字符以及冒号)但可以包含 Java 编程语言中标识符中不允许出现的字符(JLS §3.8)。
签名依赖于一种被称为类型签名的非终端元素的层级结构:

  • 一个 Java 类型签名表示的是 Java 编程语言中的引用类型或基本类型。

    JavaTypeSignature:
    ReferenceTypeSignature
    BaseType

  • 引用类型签名代表 Java 编程语言中的引用类型,即类或接口类型、类型变量或数组类型。
    类类型签名表示(可能带参数的)类或接口类型。类类型签名必须以某种方式编写,以便能够可靠地映射到其所表示的类的二进制名称,即要消除所有类型参数并将每个. 字符转换为 $ 字符。
    类型变量签名表示类型变量。
    数组类型签名表示数组类型的一维部分。

    ReferenceTypeSignature:
    ClassTypeSignature
    TypeVariableSignature
    ArrayTypeSignature

ClassTypeSignature:
L [PackageSpecifier]
SimpleClassTypeSignature {ClassTypeSignatureSuffix} ;

PackageSpecifier:
Identifier / {PackageSpecifier}

SimpleClassTypeSignature:
Identifier [TypeArguments]

TypeArguments:
< TypeArgument {TypeArgument} >

TypeArgument:
[WildcardIndicator] ReferenceTypeSignature
*

WildcardIndicator:
+
-

ClassTypeSignatureSuffix:
. SimpleClassTypeSignature

TypeVariableSignature:
T Identifier ;

ArrayTypeSignature:
[ JavaTypeSignature

类签名包含了关于(可能为通用的)类声明的类型信息。它描述了该类的任何类型参数,并列出了其(可能为参数化的)直接父类和直接父接口(如果有的话)。类型参数通过其名称来描述,随后是任何类约束和接口约束。

ClassSignature:
[TypeParameters] SuperclassSignature {SuperinterfaceSignature}

TypeParameters:
< TypeParameter {TypeParameter} >

TypeParameter:
Identifier ClassBound {InterfaceBound}

ClassBound:
: [ReferenceTypeSignature]

InterfaceBound:
: ReferenceTypeSignature

SuperclassSignature:
ClassTypeSignature

SuperinterfaceSignature:
ClassTypeSignature

方法签名包含了关于(可能为泛型的)方法声明的类型信息。它描述了该方法的任何类型参数;任何形式参数的(可能参数化的)类型;如果有的话,方法的返回类型(可能也是参数化的);以及方法的 throws 子句中声明的任何异常的类型。

MethodSignature:
[TypeParameters] ( {JavaTypeSignature} ) Result {ThrowsSignature}

Result:
JavaTypeSignature
VoidDescriptor

ThrowsSignature:
^ ClassTypeSignature
^ TypeVariableSignature

字段签名用于对字段、形式参数或局部变量声明的(可能带有参数的)类型进行编码。

FieldSignature:
ReferenceTypeSignature

SourceFile 属性

“SourceFile”属性是“类文件结构”的“属性表”中的一项可选的固定长度属性。
在“类文件结构”的“属性表”中,最多只能有一个“SourceFile”属性。
“SourceFile”属性的格式如下:

SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}

“SourceFile_attribute”结构的各个项如下:

attribute_name_index

“attribute_name_index”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构(§4.4.7),表示字符串“SourceFile”。

attribute_length

“SourceFile_attribute”结构的“attribute_length”项的值必须为 2。

sourcefile_index

“sourcefile_index”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构,表示一个字符串。
由“sourcefile_index”项引用的字符串将被解释为表示此类文件编译自的源文件的名称。它不会被解释为表示包含该文件的目录的名称或文件的绝对路径名称;此类平台特定的附加信息必须由运行时解释器或开发工具在实际使用文件名时提供。

SourceDebugExtension 属性

“SourceDebugExtension”属性是“类文件结构”(第 4.1 节)的“属性表”中的一项可选属性。
在“类文件结构”的“属性表”中,最多只能有一个“SourceDebugExtension”属性。
“SourceDebugExtension”属性的格式如下:

SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}

“SourceDebugExtension_attribute”结构的各个项如下:
attribute_name_index
attribute_name_index 项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个表示字符串“SourceDebugExtension”的 CONSTANT_Utf8_info 结构。
attribute_length
attribute_length 项的值表示该属性的长度(不包括开头的六字节)。 debug_extension[]
debug_extension 数组保存了扩展的调试信息,这些信息对 Java 虚拟机没有语义影响。该信息使用修改后的 UTF-8 字符串表示,没有终止零字节。
请注意,debug_extension 数组可能表示的字符串长度超过了可以由 String 类的实例表示的长度。

LineNumberTable属性

“LineNumberTable”属性是“code”属性的属性表中的一项可选的变长属性。它可被调试器用于确定代码数组中的哪一部分对应于原始源文件中的给定行号。 如果“代码”属性的属性表中存在多个“LineNumberTable”属性,那么它们可以以任意顺序出现。
在“代码”属性的属性表中,一个源文件的每一行可能有不止一个“LineNumberTable”属性。也就是说,“LineNumberTable”属性可能共同表示源文件中的某一行,并且不必与源行一一对应。
“LineNumberTable”属性的格式如下:

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];
}

LineNumberTable_attribute 结构中的各项如下:
attribute_name_index
attribute_name_index 项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个表示字符串“LineNumberTable”的 CONSTANT_Utf8_info 结构。
attribute_length
attribute_length 项的值表示该属性的长度(不包括开头的六字节)。
line_number_table_length
line_number_table_length 项的值表示 line_number_table 数组中的条目数量。
line_number_table[]
line_number_table 数组中的每个条目表示在代码数组中的某个点,原始源文件中的行号发生了变化。每个 行号表项必须包含以下两项:
起始程序计数器(start_pc)
起始程序计数器项的值必须指示代码数组中的索引位置, 即原始源文件中新行的代码开始位置。 起始程序计数器的值必须小于此行号表所属性的“代码”属性中的“代码长度”(code_length)项的值。
行号(line_number)
行号项的值必须给出原始源文件中对应的行号。

LocalVariableTable 属性

“局部变量表”属性是代码属性的属性表中一个可选的可变长度属性。它可被调试器用于在方法执行期间确定给定局部变量的值。如果代码属性的属性表中存在多个“局部变量表”属性,那么它们可以以任意顺序出现。
在代码属性的属性表中,每个局部变量最多只能有一个“局部变量表”属性。
“局部变量表”属性的格式如下:

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];
}

LocalVariableTable_attribute 结构中的各项如下:
attribute_name_index
attribute_name_index 项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构,表示字符串“LocalVariableTable”。
attribute_length
attribute_length 项的值表示该属性的长度(不包括开头的 6 个字节)。
local_variable_table_length
local_variable_table_length 项的值表示 local_variable_table 数组中的条目数量。
local_variable_table[]
local_variable_table 数组中的每个条目表示代码数组中一个局部变量的值所在的范围。它还表示当前帧中该局部变量在局部变量数组中的索引,该索引可用于访问该局部变量。找到了。每个条目都必须包含以下五个项:
start_pc、length
所给定的局部变量在代码数组中的索引区间 [start_pc, start_pc + length) 内必须具有值,即在 start_pc 包含且 start_pc + length 排除的范围内。 start_pc 的值必须是此代码属性中代码数组的有效索引,并且必须是指令操作码的索引。 start_pc + length 的值要么是此代码属性中代码数组的有效索引,并且是指令操作码的索引,要么必须是该代码数组末尾之后的第一个索引。
name_index
name_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须包含一个 CONSTANT_Utf8_info 结构,该结构表示一个有效的未限定名称,用于表示局部变量。
descriptor_index
descriptor_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须包含一个 CONSTANT_Utf8_info 结构,该结构表示一个字段描述符,它编码了源程序中局部变量的类型。索引 给定的局部变量必须位于当前帧的局部变量数组中的某个索引位置。
如果该索引处的局部变量的类型为双精度型或长整型,那么它会占据该索引以及该索引加 1 的位置。

LocalVariableTypeTable 属性

“LocalVariableTypeTable”属性是code属性的属性表中一个可选的可变长度属性。它可被调试器用于在方法执行期间确定给定局部变量的值。如果在给定的代码属性的属性表中存在多个“局部变量类型表”属性,那么它们可以以任意顺序出现。
在代码属性的属性表中,每个局部变量可能最多只有一个“局部变量类型表”属性。 “LocalVariableTypeTable”属性与“LocalVariableTable”属性不同之处在于,它提供的是签名信息而非描述符。信息。这种差异仅在变量的类型使用了类型变量或参数化类型时才会显现出来。
这类变量会在两个表格中都出现,而其他类型的变量则只会出现在“局部变量表”中。 “LocalVariableTypeTable”属性的格式如下:

LocalVariableTypeTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_type_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
} local_variable_type_table[local_variable_type_table_length];
}

LocalVariableTypeTable_attribute 结构中的各项如下:
attribute_name_index
attribute_name_index 项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构,表示字符串“LocalVariableTypeTable”。
attribute_length
attribute_length 项的值表示该属性的长度(不包括开头的六字节)。
local_variable_type_table_length
local_variable_type_table_length 项的值表示 local_variable_type_table 数组中的条目数量。
local_variable_type_table[]
local_variable_type_table 数组中的每个条目表示代码数组中一个局部变量值的范围。它还表示当前帧中该局部变量在局部变量数组中的索引,可通过该索引找到该局部变量。每个条目必须包含以下五个项:
start_pc, length
给定的局部变量在代码数组中的索引区间 [start_pc, start_pc + length) 中必须具有值,即从 start_pc 包含到 start_pc + length 不包含。start_pc 的值必须是此 Code 属性的代码数组中的有效索引,并且必须是指令操作码的索引。
start_pc + length 的值要么必须是此 Code 属性的代码数组中的有效索引,并且是指令操作码的索引, 要么必须是该代码数组末尾之后的第一个索引。
name_index
name_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须包含一个 CONSTANT_Utf8_info 结构,该结构表示一个有效的未限定名称,用于标识局部变量。
signature_index
signature_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须包含一个 CONSTANT_Utf8_info 结构,该结构表示一个字段签名,它编码了源程序中局部变量的类型。索引 给定的局部变量必须位于当前帧的局部变量数组中的某个索引位置。
如果该索引处的局部变量的类型为双精度型或长整型,那么它会占据该索引以及该索引加 1 的位置。

Deprecated属性

“Deprecated”属性是类文件、字段信息或方法信息结构的属性表中一个可选的固定长度属性。 可以使用“Deprecated”属性来标记类、接口、方法或字段,以表明该类、接口、方法或字段已被取代。
运行时解释器或读取类文件格式的工具(例如编译器)可以利用这种标记来告知用户正在引用已被取代的类、接口、方法或字段。存在“已弃用”属性并不会改变类或接口的语义。
“Deprecated”属性的格式如下:

Deprecated_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
}

“Deprecated”结构中的各项如下:
attribute_name_index
“属性名称索引”项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构,该结构表示字符串“已弃用”。
attribute_length
“attribute_length”项的值必须为零。

RuntimeVisibleAnnotations 属性

“RuntimeVisibleAnnotations”属性是类文件、字段信息或方法信息结构(的属性表中的一种可变长度属性。该属性记录了对应类、字段或方法声明的运行时可见注释。最终的译文:这位男士说道。
Java 虚拟机必须提供这些注解,以便它们能够通过相应的反射 API 返回。 在类文件、字段信息或方法信息结构的属性表中,最多只能有一个“RuntimeVisibleAnnotations”属性。
“RuntimeVisibleAnnotations”属性的格式如下:

RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}

“RuntimeVisibleAnnotations_attribute 结构中的各项如下”以下内容:
attribute_name_index
属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“RuntimeVisibleAnnotations”的 CONSTANT_Utf8_info 结构(第 4.4.7 节)。
attribute_length
属性长度项的值表示该属性的长度(不包括开头的六字节)。
num_annotations
num_annotations 项的值给出了由该结构表示的运行时可见注释的数量。
annotations[] 注释表中的每个条目代表声明上的一个运行时可见注释。注释结构具有以下内容。格式:

 annotation {
 u2 type_index;
 u2 num_element_value_pairs;
 { u2 element_name_index;
 element_value value;
 } element_value_pairs[num_element_value_pairs];
}

注释结构的各个项如下:
type_index
type_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构(第 4.4.7 节),该结构表示一个字段描述符(第 4.3.2 节)。字段描述符指定了由此注释结构表示的注释的类型。
num_element_value_pairs
num_element_value_pairs 项的值给出了由此注释表示的元素-值对的数量结构。
element_value_pairs[]
“element_value_pairs”表中的每个值都代表了由该注释结构所表示的注释中的一个单独的元素值对。每个 元素值对“entry”包含以下两项:
element_name_index
元素名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构(参见第 4.4.7 节)。该常量池条目表示由此“元素值对”条目所代表的元素值对的元素的名称。
换句话说,该条目表示由“类型索引”指定的注解类型的元素。
value
该值项的值表示由此“元素 - 值对”条目所代表的元素 - 值配对的值。

element_value结构

“element_value”结构是一种区分型联合体,用于表示一个“元素值对”的值。其格式如下:

element_value {
u1 tag;
union {
u2 const_value_index;
{ u2 type_name_index;
u2 const_name_index;
} enum_const_value;
u2 class_info_index;
annotation annotation_value;
{ u2 num_values;
element_value values[num_values];
} array_value;
} value;
}

该标签项使用一个 ASCII 字符来指示元素值对中值的类型。这决定了值联合中正在使用的哪一项。 表 4.7.16.1-A 列出了标签项的有效字符、每个字符所表示的类型以及每个字符在值联合中所使用的项。该表的第四列在下面对值联合的一项的描述中会用到。

image.png

该值项表示一个元素-值对的值。该项是一个联合体, 其自身的项如下:
const_value_index
const_value_index 项表示要么是一个基本常量值,要么是一个字符串字面量,作为此元素-值对的值。
const_value_index 项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是表 4.7.16.1 中第四列所指定的类型,与标签项相匹配。
enum_const_value
“enum_const_value”这一项表示该元素 - 值对的值为一个枚举常量。

“enum_const_value”这一项包含以下两个项:
type_name_index
类型名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构(§4.4.7),代表一个字段描述符(§4.3.2)。常量池条目给出了由此元素 - 值结构表示的枚举常量的二进制名称的内部形式(§4.2.1)。
const_name_index
常量名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构(§4.4.7)。常量池条目给出了由此元素 - 值表示的枚举常量的简单名称结构。

class_info_index
“class_info_index”项表示该元素值对的值为一个类常量。 “class_info_index”项必须是常量池中的有效索引。表格。在该索引处的常量池条目必须是一个“CONSTANT_Utf8_info”结构(参见第 4.4.7 节),该结构表示一个返回描述符(参见第 4.3.3 节)。返回描述符给出了由此元素值结构所表示的类字面量所对应的类型。类型与类字面量的对应关系如下:

  • 对于类字面量 C.class(其中 C 是类、接口或数组类型的名称),其对应的类型为 C。常量池中的返回描述符将为对象类型或数组类型。
  • 对于类字面量 p.class(其中 p 是基本类型的名称),其对应的类型为 p。常量池中的返回描述符将为基本类型字符。
  • 对于类字面量 void.class,其对应的类型为 void。常量池中的返回描述符将为 V。 例如,类字面量 Object.class 对应于类型 Object,因此常量池中的条目是 Ljava/lang/Object;,而类字面量 int.class 对应于类型 int,因此常量池中的条目是 I。
    类字面量 void.class 对应于 void,因此常量池中的条目是 V,而类字面量 Void.class 对应于类型 Void,因此常量池中的条目是 Ljava/lang/Void;。

annotation_value
注解值项表示此元素-值对的“嵌套”注解。 注解值项的值是一个注解结构(§4.7.16),它给出了此元素-值结构所表示的注解。 数组值“array_value”项表示此元素值对的值为一个数组。
“array_value”项包含以下两个项:
“num_values”
“num_values”项的值表示由此元素值结构所代表的数组中的元素数量。
“values[]”
“values”表中的每个值都给出了由此元素值结构所代表的数组的相应元素。

RuntimeInvisibleAnnotations属性

“RuntimeInvisibleAnnotations”属性是位于类文件、字段信息或方法信息结构(的属性表中的一个可变长度属性。该属性记录了对应于相应类、方法或字段声明的运行时不可见注释。在一个类文件、字段信息或方法信息结构的属性表中,最多只能有一个“RuntimeInvisibleAnnotations”属性。

“RuntimeInvisibleAnnotations”属性与“RuntimeVisibleAnnotations”属性类似(§4.7.16),不同之处在于,“RuntimeInvisibleAnnotations”属性所表示的注释不得通过反射 API 返回,除非 Java 虚拟机通过某种特定于实现的机制(例如命令行标志)被指示保留这些注释。如果没有此类指示,Java 虚拟机将忽略此属性。

“RuntimeInvisibleAnnotations”属性的格式如下:

RuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}

“RuntimeInvisibleAnnotations_attribute 结构的各个项如下所示”以下内容:

attribute_name_index
属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“运行时不可见注释”的 CONSTANT_Utf8_info 结构(第 4.4.7 节)。

attribute_length 属性长度项的值表示该属性的长度(不包括最初的六字节)。

num_annotations num_annotations 项的值给出了由该结构表示的运行时不可见注释的数量。

annotations[] 注释表中的每个条目代表声明上的单个运行时不可见注释。注释结构在第 4.7.16 节中有详细说明。

RuntimeVisibleParameterAnnotations属性

“运行时可见参数注释”属性是方法信息结构的属性表中的一种可变长度属性。 “运行时可见参数注释”属性记录了运行时可见的信息。

Java 虚拟机必须提供这些注释,以便它们能够通过适当的反射 API 返回。

在一个方法信息结构的属性表中,最多只能有一个“RuntimeVisibleParameterAnnotations”属性。

“RuntimeVisibleParameterAnnotations”属性的格式如下:

   RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}

“RuntimeVisibleParameterAnnotations_attribute 结构的各个项”如下所示:

attribute_name_index 属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个 CONSTANT_Utf8_info 结构,表示字符串“RuntimeVisibleParameterAnnotations”。

attribute_length 属性长度项的值表示该属性的长度(不包括初始的六字节)。

num_parameters 属性数量项给出由发生该注解的方法信息结构所表示的方法的正式参数的数量。 这重复了可以从方法描述符中提取的信息。

parameter_annotations[] 参数注释表中的每个条目表示单个正式参数声明上的所有运行时可见注释。 表中的第 i 个条目对应于方法描述符中的第 i 个正式参数(§4.3.3)。每个参数注释条目包含以下两个项:

  • num_annotations “num_annotations”项的值表示与“parameter_annotations”条目对应的形参声明上的可运行可见注释的数量。

  • annotations[] 注释表中的每个条目代表与“parameter_annotations”条目对应的形参声明上的单个可运行可见注释。注释结构在第 4.7.16 节中有详细说明。

RuntimeVisibleTypeAnnotations属性

“运行时可见类型注释”属性是类文件、字段信息或方法信息结构中属性表中的一个可变长度属性,或者是在代码属性中。该属性记录了在相应类、字段或方法的声明中,或在相应表达式中所使用的类型上的运行时可见注释。运行时可见类型注释属性还会记录泛型类、接口、方法和构造函数类型参数声明的运行时可见注释。Java 虚拟机必须提供这些注释,以便通过适当的反射 API 返回它们。

在一个 ClassFile、field_info 或 method_info 结构或 Code 属性表中,运行时可见类型注释属性最多只能有一个。 属性表中仅当类型在与属性表的父结构或属性相对应的声明或表达式类型中进行注释时,才会包含运行时可见类型注释属性。 例如,类声明的实现子句中的所有类型注释都记录在类的 ClassFile 的运行时可见类型注释属性中。结构。与此同时,字段声明中关于类型的所有注释都被记录在该字段的“field_info”结构中的“RuntimeVisibleTypeAnnotations”属性中。

“RuntimeVisibleTypeAnnotations”属性的格式如下:

RuntimeVisibleTypeAnnotations_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u2 num_annotations;
 type_annotation annotations[num_annotations];
}

“RuntimeVisibleTypeAnnotations_attribute 结构的各个项如下所示”以下内容:

attribute_name_index 属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“RuntimeVisibleTypeAnnotations”的 CONSTANT_Utf8_info 结构。

attribute_length 属性长度项的值表示该属性的长度(不包括最初的六字节)。

num_annotations 注解数量项的值给出了由该结构表示的运行时可见类型注解的数量。

annotations[] 注解表中的每个条目代表声明或表达式中使用的类型上的单个运行时可见注解。

类型注解结构具有以下格式:

type_annotation {
 u1 target_type;
 union {
 type_parameter_target;
 supertype_target;
 type_parameter_bound_target;
 empty_target;
 method_formal_parameter_target;
 throws_target;
 localvar_target;
 catch_target;
 offset_target;
 type_argument_target;
 } target_info;
 type_path target_path;
 u2 type_index;
 u2 num_element_value_pairs;
 { u2 element_name_index;
 element_value value;
 } element_value_pairs[num_element_value_pairs];
}

前三个项——目标类型、目标信息和目标路径——指明了已标注类型的确切位置。最后三个项——类型索引、元素值对数量以及元素值对数组——则指明了注释自身的类型和元素值对。 类型注释结构的各个项如下:

target_type 目标类型项的值表示注释出现的目标类型种类。各种目标类型对应于 Java 编程语言中在声明和表达式中使用类型的类型上下文(JLS 第 4.11 节)。 目标类型值的合法值在表 4.7.20-A 和表 4.7.20-B 中指定。每个值都是一个字节标签,指示目标信息联合中的哪个项紧随目标类型项之后,以提供更多关于目标的信息。 表 4.7.20-A 和表 4.7.20-B 中的目标类型种类对应于 JLS 第 4.11 节中的类型上下文。具体而言,目标类型值 0x10 - 0x17 和 0x40 - 0x42 对应于类型上下文 1 - 10,而目标类型值 0x43 - 0x4B 对应于类型上下文 11 - 16。 目标类型项的值决定了类型注释结构是否出现在……在类文件结构、字段信息结构、方法信息结构或代码属性中,存在“运行时可见类型注释”属性。 表 4.7.20-C 给出了对于具有每个合法目标类型值的类型注释结构的“运行时可见类型注释”属性的位置。

target_info 目标_info 项的值精确地表示声明或表达式中哪个类型被注释。 目标_info 联合体的各个项在 §4.7.20.1 中有明确说明。

target_path 目标_path 项的值精确地表示由目标_info 表示的类型中的哪一部分被注释。 类型_path 结构的格式在 §4.7.20.2 中有明确说明。

type_index、num_element_value_pairs、element_value_pairs[] 类型注释结构中这些项的含义与注释结构(§4.7.16)中的含义相同。

image.png

image.png

image.png

target_info 联合体

目标信息联合体中的各项(除了第一个项之外)精确地指明了在声明或表达式中被标注的是哪种类型。第一个项并非指明哪种类型,而是指明的是某个类型参数的哪个声明被标注了。这些项是如下所示:

  • “type_parameter_target”项表示某个注释出现在泛型类、泛型接口、泛型方法或泛型构造函数的第 i 个类型参数的声明处。
type_parameter_target {
 u1 type_parameter_index;
}

“类型参数索引”项的值指明了哪个类型参数声明已被标注。类型参数索引值为 0 表示第一个类型参数声明。 4.7 属性 类文件格式146

“supertype_target”这一项表示某个注解出现在类或接口声明的“extends”或“implements”子句中的某个类型上。

supertype_target {
u2 supertype_index}

“supertype_index”值为 65535 表示该注解出现在类声明的“extends”子句中的超类中。 其他任何“supertype_index”值则是指向“EnclosingClassFile”结构中“interfaces”数组的索引,并表示该注解出现在类声明的“implements”子句或接口声明的“extends”子句中的那个超接口中。

  • “type_parameter_bound_target”项表示注解出现在泛型类、接口、方法或构造函数的第 j 个类型参数声明的第 i 个约束上。
type_parameter_bound_target {
u1 type_parameter_index;
u1 bound_index;}

“type_parameter_index”项的值指明了具有注释绑定的类型参数声明是哪一个。类型参数索引值为 0 表示第一个类型参数声明。

“bound_index i”项的值指明了由类型参数索引所指示的类型参数声明的哪个绑定被注释了。绑定索引值为 0 表示第一个类型参数声明的绑定。

“type_parameter_index”项记录了某个绑定被注释了,但并未记录构成该绑定的类型。该类型可以通过检查存储在适当的签名属性中的类签名或方法签名来找到。

  • 空目标项表示注释出现在字段声明中的类型、方法的返回类型、新构造对象的类型或方法或构造函数的接收者类型上。
empty_target { }

在这些位置中,每种类型都只出现一次,因此在目标信息联合体中无需表示每种类型的特定信息。

  • “formal_parameter_target”这一项表示,在方法、构造函数或 lambda 表达式的正式参数声明中,有一个注释出现在该类型的上方。
formal_parameter_target {
u1 formal_parameter_index}

“formal_parameter_indext”项的值指明了具有注释类型的形式参数声明是哪一个。形式参数索引值为 0 表示第一个形式参数声明。

“formal_parameter_target”项记录了形式参数的类型已进行注释,但并未记录具体的类型。该类型可通过检查包含“RuntimeVisibleTypeAnnotations”属性的方法信息结构的“方法描述符”(§4.3.3)来获取。形式参数索引值为 0 表示方法或构造函数声明的“抛出”子句中第一个参数描述符。

  • “throws_target”项表示注释出现在方法或构造函数声明的“抛出”子句中的第 i 种类型上。
throws_target { u2 throws_type_index; }

“throws_type_index”项的值是“方法信息”结构中“运行时可见类型注释”属性的“异常索引表”数组中的一个索引。

  • “localvar_target”项表示注释出现在局部变量声明中的类型上,包括在“try-with-resources”语句中声明为资源的变量。
localvar_target {
 u2 table_length;
 { u2 start_pc;
 u2 length;
 u2 index;
 } table[table_length];
}

“table_length”项的值表示表数组中的条目数量。每个条目都指示代码数组中一个局部变量的有效范围。它还指示当前帧中该局部变量在局部变量数组中的索引位置,从而能够找到该局部变量。每个条目包含以下三个项目:

start_pc, length 所给的局部变量在代码数组中的索引位置上的值位于区间 [start_pc, start_pc + length) 内,即从 start_pc 包含的起始位置到 start_pc + length 不包含的结束位置之间。

index 给定的局部变量必须位于当前帧的局部变量数组中的某个索引位置。 如果索引处的局部变量的类型为 double 或 long,则它会占用该索引以及该索引加 1 的位置。 需要一个表格来完整指定其类型已标注的局部变量。

  • “catch_target”这一项表示在异常参数声明中的第 i 种类型上会出现一个注释。
catch_target {
u2 exception_table_index;}

“exception_table_index”项的值是“代码”属性中包含“运行时可见类型注释”属性的“异常表”数组中的一个索引。 在异常参数声明中存在多种类型的可能性源于“try”语句的多捕获子句,其中异常参数的类型是多种类型组成的联合体(JLS 第 14.20 节)。通常,编译器会为联合体中的每个类型创建一个“异常表”条目,这使得“catch_target”项能够进行区分。他们。这保持了类型与其注解之间的对应关系。

  • “offset_target”项表示注解出现在“instanceof”表达式中的类型、新表达式中,或者方法引用表达式中的“::”之前的部分。
offset_target {
u2 offset}

偏移项的值指定了与 instanceof 表达式对应的 instanceof 字节码指令、与 new 表达式对应的 new 字节码指令,或者与方法引用表达式对应的字节码指令的代码数组偏移量。

  • 类型参数目标项表示注解要么出现在赋值表达式中的第 i 个类型上,要么出现在以下任何一种情况的显式类型参数列表中的第 i 个类型参数上:新表达式、显式构造函数调用语句、方法调用表达式或方法引用。表达。 类型参数目标结构体:
 offset_target { u2 offset; }

偏移项的值指定了与转换表达式对应的字节码指令、与新表达式对应的新的字节码指令、与显式构造函数调用语句对应的字节码指令、与方法调用表达式对应的字节码指令,或者与方法引用表达式对应的字节码指令的代码数组偏移量。

对于转换表达式,类型参数索引项的值指定了转换运算符中被注释的类型。类型参数索引值为 0 表示转换运算符中的第一个(或唯一)类型。

在转换表达式中存在不止一种类型的可能性源于对交集类型的转换。 对于显式类型参数列表,类型参数索引项的值指定了被注释的类型参数。类型参数索引值为 0 表示第一个类型参数。

type_path 结构

在声明或表达式中使用任何类型时,type_path 结构都会标识该类型中哪一部分进行了注释。注释可以出现在类型本身上,但如果该类型是引用类型,则还有其他位置可以出现注释:

  • 如果在声明或表达式中使用了数组类型 T[],那么在该数组类型的任何组件类型(包括元素类型)上都可以添加注解。
  • 如果在声明或表达式中使用了嵌套类型 T1.T2,那么可以在顶层类型或任何成员类型的名称上添加注解。
  • 如果在声明或表达式中使用了参数化类型 T< A >、T<? extends A > 或 T<? super A >,那么可以在任何类型参数上或任何通配符类型参数的界限上添加注解。

例如,考虑一下在以下代码中被注释的 String[][] 的不同部分:

@Foo String[][] // Annotates the class type String
String @Foo [][] // Annotates the array type String[][]
String[] @Foo [] // Annotates the array type String[]

或者在以下注释中提及的嵌套类型 Outer.Middle.Inner 的各个不同部分:

@Foo Outer.Middle.Inner
Outer.@Foo Middle.Inner
Outer.Middle.@Foo Inner

或者参数化类型“Map<String, Object>”和“List<...>”的不同部分(这些部分在以下内容中进行了注解):

@Foo Map<String,Object>
Map<@Foo String,Object>
Map<String,@Foo Object>
List<@Foo ? extends String>
List<? extends @Foo String>

“type_path”结构的格式如下:

type_path {
 u1 path_length;
 { u1 type_path_kind;
 u1 type_argument_index;
 } path[path_length];
}

“path_length”项的值表示路径数组中的条目数量:

  • 如果“path_length”的值为 0,则注释将直接显示在类型上。它本身。
  • 如果 path_length 的值不为零,那么路径数组中的每个元素都表示沿着从左至右的方向,朝着数组类型、嵌套类型或参数化类型的注释精确位置所进行的迭代步骤。在数组类型中,迭代会先访问数组类型本身,然后是其组成类型,接着是该组成类型的组成类型,依此类推,直至到达元素类型为止。每个元素包含以下两项内容:

type_path_kind 该“类型路径类型”项的合法值列于表 4.7.20.2-A 中。

image.png

type_argument_index 如果“类型路径类型”项的值为 0、1 或 2,那么“类型参数索引”项的值为 0。 如果“类型路径类型”项的值为 3,则“类型参数索引”项指定了参数化类型中哪个类型参数被注释,其中 0 表示参数化类型的第一个类型参数。

image.png

image.png

image.png

image.png

image.png

RuntimeInvisibleTypeAnnotations 属性

“RuntimeInvisibleTypeAnnotations”属性是类文件、字段信息或方法信息结构中属性表中的一个可变长度属性,或者是在“Code”属性中。该属性记录了在相应类、字段或方法的声明中,或在相应方法体中的表达式中所使用的类型上的运行时不可见注解。“RuntimeInvisibleTypeAnnotations”属性还记录了泛型类、接口、方法和构造函数的类型参数声明上的注解。

在类文件、字段信息或方法信息结构的属性表中,“RuntimeInvisibleTypeAnnotations”属性最多只能有一个。

如果在与属性表的父结构或属性相对应的声明或表达式中对类型进行了注解,则属性表中会包含“RuntimeInvisibleTypeAnnotations”属性。

“RuntimeInvisibleTypeAnnotations”属性的格式如下:

RuntimeInvisibleTypeAnnotations_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u2 num_annotations;
 type_annotation annotations[num_annotations];
}

“RuntimeInvisibleTypeAnnotations_attribute”结构中的各项为如下所示:

attribute_name_index 属性名称索引项的值必须是常量池表中的有效索引。该索引处的常量池条目必须是一个表示字符串“运行时不可见类型注释”的 CONSTANT_Utf8_info 结构。

attribute_length 属性长度项的值表示该属性的长度(不包括最初的六字节)。

num_annotations 注释数量项的值给出了由该结构表示的运行时不可见类型注释的数量。

annotations[] 注释表中的每个条目代表声明或表达式中使用的类型上的单个运行时不可见注释。类型注释结构在第 4.7.20 节中有详细说明。

AnnotationDefault属性

“AnnotationDefault”属性是某些“method_info”结构(第 4.6 节)的“属性”表中的一个可变长度属性,这些结构具体指的是表示注解类型元素的结构(JLS 第 9.6.1 节)。“AnnotationDefault”属性记录了由该“method_info”所代表的元素的默认值(JLS 第 9.6.2 节)。结构。Java 虚拟机必须提供此默认值,以便适当的反射 API 能够使用它。

在方法信息结构的属性表中,最多只能有一个 AnnotationDefault 属性,它代表一个注解类型的元素。

AnnotationDefault 属性的格式如下:

AnnotationDefault_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 element_value default_value;
}

“AnnotationDefault_attribute”结构的各个项如下:

attribute_name_index “attribute_name_index”项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个表示字符串“AnnotationDefault”的 CONSTANT_Utf8_info 结构。

attribute_length “attribute_length”项的值表示该属性的长度(不包括开头的六字节)。

default_value “default_value”项表示由包含此“AnnotationDefault”属性的方法信息结构所 代表的注解类型元素的默认值。

BootstrapMethods属性

“BootstrapMethods”属性是类文件结构(§4.1)的属性表中的一种可变长度属性。该属性记录了由“invokedynamic”指令引用的引导方法说明符。

如果类文件结构的常量池表至少有一个“CONSTANT_InvokeDynamic_info”条目(§4.4.10),那么在类文件结构的属性表中必须恰好有一个“BootstrapMethods”属性。 在类文件结构的属性表中,最多只能有一个“BootstrapMethods”属性。

“BootstrapMethods”属性的格式如下:

BootstrapMethods_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u2 num_bootstrap_methods;
 { u2 bootstrap_method_ref;
 u2 num_bootstrap_arguments;
 u2 bootstrap_arguments[num_bootstrap_arguments];
 } bootstrap_methods[num_bootstrap_methods];
}

“BootstrapMethods_attribute”结构的各个项如下:

attribute_name_index “attribute_name_index”项的值必须是常量池表中的有效索引。 该索引处的常量池条目必须是一个表示字符串“BootstrapMethods”的 CONSTANT_Utf8_info 结构。

attribute_length “attribute_length”项的值表示该属性的长度(不包括开头的六字节)。 因此,该值取决于此 ClassFile 结构中调用动态指令的数量。

num_bootstrap_methods “num_bootstrap_methods”项的值确定了“bootstrap_methods”数组中的 bootstrap 方法说明符的数量。

bootstrap_methods[] “bootstrap_methods”表中的每个条目都包含一个指向 CONSTANT_MethodHandle_info 结构(第 4.4.8 节)的索引,该结构指定了一个 bootstrap 方法,并且可能为空的静态参数索引序列用于该 bootstrap 方法。

每个“bootstrap_methods”条目都必须包含以下三项内容:

“bootstrap_method_ref” “bootstrap_method_ref”项的值必须是“常量池”表中有效的一个索引。该索引处的“常量池”条目必须是一个“CONSTANT_MethodHandle_info”结构。

方法柄的形式由 §invokedynamic 中调用位置说明符的持续解析决定,在 §invokedynamic 中,执行“java.lang.invoke.MethodHandle”的“invoke”操作时,要求将“bootstrap 方法柄”调整为传递的实际参数,就好像通过“java.lang.invoke.MethodHandle.asType”进行调用一样。因此,根据上述规定, CONSTANT_MethodHandle_info 结构中的 reference_kind 项应具有值 6 或 8(§5.4.3.5),而 reference_index 项应指定一个接受三个参数(按顺序为 java.lang.invoke.MethodHandles.Lookup、String 和 java.lang.invoke.MethodType)的静态方法或构造函数。否则,在调用点说明符解析期间对引导方法句柄的调用将会突然结束。

num_bootstrap_arguments num_bootstrap_arguments 项的值给出了 bootstrap_arguments 数组中的元素数量。

bootstrap_arguments[] bootstrap_arguments 数组中的每个条目都必须是常量池表中的有效索引。该索引处的常量池条目必须是 CONSTANT_String_info、CONSTANT_Class_info、CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_MethodHandle_info 或 CONSTANT_MethodType_info 结构。

MethodParameter属性

“MethodParameters”属性是方法信息结构的属性表中的一种可变长度属性。该属性记录了方法的形参信息,例如它们的名称。

在方法信息结构的属性表中,最多只能有一个“MethodParameters”属性。

“MethodParameters”属性的格式如下:

MethodParameters_attribute {
 u2 attribute_name_index;
 u4 attribute_length;
 u1 parameters_count;
 { u2 name_index;
 u2 access_flags;
 } parameters[parameters_count];
}

“MethodParameters_attribute”结构中的各项如下:

attribute_name_index attribute_name_index 项的值必须是常量池表中有效索引的值。该索引处的常量池条目必须是一个表示字符串“MethodParameters”的 CONSTANT_Utf8_info 结构。

attribute_length attribute_length 项的值表示该属性的长度(不包括开头的六字节)。

parameters_count parameters_count 项的值表示该属性所引用的外部方法_info 结构的描述符索引所指向的方法描述符中的参数描述符数量。

这并非 Java 虚拟机实现在格式检查期间必须强制执行的约束(§4.8)。在方法描述符中匹配参数描述符与下面参数数组中的项的任务是由 Java SE 平台的反射库完成的。

parameters[] 参数数组中的每个条目都包含以下两个项:

name_index 名称索引项的值必须为零或者是在常量池表中的有效索引。 如果名称索引项的值为零,则此参数元素表示一个没有名称的形参。 如果名称索引项的值不为零,则在该索引处的常量池条目必须是一个表示有效未限定名称的 CONSTANT_Utf8_info 结构。

access_flags 访问标志项的值如下:

0x0010(ACC_FINAL) 表示该形参是在声明时被声明为最终的。

0x1000(ACC_SYNTHETIC) 表示该形参并非根据编写源代码所使用的语言的规范在源代码中显式或隐式声明的(JLS §13.1)。(该形参是编译器生成此类文件时的实现细节,而非源代码中的实际声明。)

0x8000(ACC_MANDATED) 表示该形参是根据编写源代码所使用的语言的规范在源代码中隐式声明的(JLS §13.1)。(该形式参数是由语言规范所规定的,因此该语言的所有编译器都必须生成该参数。)

参数数组中的第 i 个元素对应于包含方法描述符的外部方法中的第 i 个参数描述符。(“参数数量”项占用一个字节,因为方法描述符最多只能有 255 个参数。)实际上,这意味着参数数组存储了该方法所有参数的信息。可以设想其他方案,即参数数组中的每个元素指定其对应的参数描述符,但这会过度复杂化“方法参数”属性。

参数数组中的第 i 个元素可能与包含方法的签名属性(如果存在)中的第 i 个类型相对应,也可能与包含方法的参数注释中的第 i 个注解相对应。

格式检查

当 Java 虚拟机加载一个待处理的类文件时(参见第 5.3 节),它首先会确保该文件具有类文件的基本格式(参见第 4.1 节)。这一过程被称为格式检查。检查内容如下:

  • 前四个字节必须包含正确的魔法号码。
  • 所有已识别的属性必须具有适当的长度。
  • 类文件不能被截断,也不能在末尾有额外的字节。
  • 常量池必须满足第 4.4 节中所记录的所有约束条件。 例如,常量池中的每个 CONSTANT_Class_info 结构在其 name_index 项中必须包含一个有效的常量池索引,该索引对应于 CONSTANT_Utf8_info 结构。
  • 常量池中的所有字段引用和方法引用都必须具有有效的名称、有效的类以及有效的描述符(§4.3)。 格式检查并不能确保所给定的字段或方法确实存在于所给定的类中,也不能确保所给出的描述符所指向的确实是真实的类。格式 检查仅能确保这些项目格式正确。当对字节码本身进行验证以及在解析过程中,会进行更详细的检查。 这些针对基本类文件完整性的检查对于解读类文件内容是必不可少的。格式检查与字节码验证是不同的,尽管从历史上看它们曾被混淆,因为两者都是完整性的一种形式。确认。

Java 虚拟机代码的限制条件

方法、实例初始化方法或类或接口初始化方法的代码存储在类文件的 method_info 结构的 Code 属性的代码数组中。本节描述了 Code_attribute 结构内容所涉及的约束条件。

静态限制

类文件中的静态约束条件是指那些用于定义其格式正确性的那些条件。文件。在前几节中已经给出了这些约束条件,但不包括类文件中代码的静态约束条件。类文件中代码的静态约束条件规定了 Java 虚拟机指令在代码数组中的布局方式以及每个指令的操作数必须是什么。 代码数组中指令的静态约束条件如下:

  • 只有在第 6.5 节中记录的指令实例才能出现在代码数组中。使用保留操作码(第 6.2 节)或本规范中未记录的任何操作码的指令实例不得出现在代码数组中。 如果类文件版本号为 51.0 或更高,则代码数组中不得出现 jsr 操作码或 jsr_w 操作码。
  • 代码数组中第一条指令的操作码从索引 0 开始。
  • 对于代码数组中的除最后一条指令之外的每条指令,下一条指令的操作码索引等于当前指令的操作码索引加上该指令的长度(包括其所有操作数)。 宽指令在这些方面与任何其他指令一样处理;“the” 指定宽指令所要修改的操作的指令码被视为该宽指令的一个操作数。该指令码绝不能通过计算直接获取。
  • 代码数组中最后一条指令的最后一个字节必须是索引为 code_length - 1 的字节。 代码数组中指令的操作数所受的静态约束如下:以下内容: 每个跳转和分支指令(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)的目标必须是此方法中某条指令的指令码。 类文件格式 - Java 虚拟机代码的约束条件 4.9161 跳转或分支指令的目标绝不能是用于指定由宽指令修改的操作的指令码;跳转或分支的目标可以是宽指令本身。
  • 每个表切换指令的每个目标(包括默认值),以及每个表切换指令的跳转表中的每个目标,都必须是此方法中某条指令的指令码。 每个表切换指令的跳转表中的条目数量必须与它的低跳转表操作数和高跳转表操作数的值相一致,并且它的低值必须小于或等于它的高值。 表切换指令的每个目标都不能是用于指定由宽指令修改的操作的指令码;表切换的目标可以是宽指令本身。
  • 每个查找切换指令的每个目标(包括默认值),以及每个查找切换指令的跳转表中的每个目标,都必须是此方法中某条指令的指令码。 每个查找切换指令必须具有与它的 npairs 操作数的值相一致的匹配偏移量对数量。这些匹配偏移量对必须按带符号匹配值的递增数值顺序排列。 查找切换指令的每个目标都不能是用于指定由宽指令修改的操作的指令码;查找开关的目标可以是 一条宽指令本身。
  • 每条 ldc 指令和每条 ldc_w 指令的操作数都必须是常量池表中的有效索引。由该索引所引用的常量池条目必须是以下类型之一:
  • 如果类文件版本号小于 49.0,则为 CONSTANT_Integer、CONSTANT_Float 或 CONSTANT_String。
  • 如果类文件版本号为 49.0 或 50.0,则为 CONSTANT_Integer、CONSTANT_Float、CONSTANT_String 或 CONSTANT_Class。
  • 如果类文件版本号为 51.0 或更高,则为 CONSTANT_Integer、CONSTANT_Float、CONSTANT_String、CONSTANT_Class、CONSTANT_MethodType 或 CONSTANT_MethodHandle。
  • 每条 ldc2_w 指令的操作数必须表示常量池表中的有效索引。由该索引所引用的常量池条目必须是 CONSTANT_Long 或 CONSTANT_Double 类型。 随后的常量池索引也必须是常量池中的有效索引,并且该索引处的常量池条目不得被使用。
  • 每条 getfield、putfield、getstatic 和 putstatic 指令的操作数都必须是常量池表中的有效索引。该索引所引用的内容必须为“常量字段引用”类型。
  • 每个“调用虚方法”指令的“索引字节”操作数必须表示常量池表中的有效索引。由该索引引用的常量池条目必须为“常量方法引用”类型。
  • 每个“调用特殊方法”和“调用静态方法”指令的“索引字节”操作数必须表示常量池表中的有效索引。如果类文件版本号小于 52.0,则由该索引引用的常量池条目必须为“常量方法引用”类型;如果类文件版本号为 52.0 或更高,则由该索引引用的常量池条目必须为“CONSTANT_M”类型。

结构约束

代码数组的结构约束对 Java 虚拟机指令之间的关系也有所规定。这些结构约束的具体内容如下:以下内容:

  • 每条指令的执行都必须在操作数栈和局部变量数组中使用恰当的类型和数量的参数,无论其调用路径如何。 对整型值进行操作的指令也可以对布尔型、字节型、字符型和短型值进行操作。 如第 2.3.4 节和第 2.11.1 节所述,Java 虚拟机会在内部将布尔型、字节型、短型和字符型的值转换为整型。
  • 如果一条指令可以沿着多个不同的执行路径执行,那么在执行该指令之前,操作数栈的深度必须与(第 2.6.2 节所述)相同的深度一致,无论所选择的路径如何。
  • 在执行过程中,操作数栈的深度绝不能超过“最大栈深度”项所暗示的深度。
  • 在执行过程中,操作数栈中弹出的值数量绝不能多于其包含的值数量。
  • 在执行过程中,不能颠倒持有长型或双精度值的局部变量对的顺序,也不能将其拆分。在这样的局部变量对中,其局部变量不能单独进行操作。
  • 任何局部变量(对于值类型为长型或双精度型的情况,则为局部变量对)都不能单独进行操作。(double类型)在被赋值之前就可以被访问。
  • 每个调用特殊指令的操作必须指定一个实例初始化方法(§2.9),即当前类或接口中的方法、当前类的超类中的方法、当前类或接口的直接超接口中的方法,或者 Object 类中的方法。 当调用实例初始化方法时,必须在操作数栈中有一个未初始化的类实例处于适当的位置。实例初始化方法绝不能在已初始化的类实例上被调用。 如果一个 invokespecial 指令指定了一个实例初始化方法,并且操作数栈上的目标引用是当前类的未初始化的类实例,则 invokespecial 必须指定当前类或其直接超类的实例初始化方法。 Java 虚拟机代码的类文件格式 4.9 限制条件165 如果一个“invokespecial”指令指定了一个实例初始化方法,并且操作数栈上的目标引用是由之前的一条“new”指令创建的类实例所指向的,那么“invokespecial”指令必须指定该类实例所属类中的一个实例初始化方法。 如果一个“invokespecial”指令所指的方法不是实例初始化方法,那么操作数栈上的目标引用的类型必须与当前类的类型兼容(根据《Java 语言规范》第5.2节)。 • 除了从类“Object”的构造函数派生出来的实例初始化方法之外,每个实例初始化方法都必须在访问其实例成员之前调用本类的另一个实例初始化方法或者其直接父类“super”的实例初始化方法。 然而,本类中在当前类中声明的实例字段可以在调用任何实例初始化方法之前进行赋值。 • 当调用任何实例方法或者访问任何实例变量时,包含该实例方法或实例变量的类实例必须已经完成初始化。• 如果代码中存在受异常处理程序保护的局部变量中未初始化的类实例,那么: 1)如果该处理程序位于 方法内部,则该处理程序必须抛出异常或陷入死循环; 2)如果该处理程序不位于 方法内部,则该未初始化的类实例必须保持未初始化状态。
  • 在执行 jsr 或 jsr_w 指令时,操作数栈或局部变量中绝不能存在未初始化的类实例。
  • 每个作为方法调用目标的类实例的类型必须与指令中指定的类或接口类型在赋值兼容性方面相匹配(JLS 第 5.2 节)。
  • 每个方法调用的参数类型必须与方法描述符中的方法调用兼容性相匹配(JLS 第 5.3 节、第 4.3.3 节)。
  • 每个返回指令必须与其方法的返回类型相匹配:
    • 如果方法返回布尔值、字节、字符、短整数或整数,则只能使用 ireturn 指令;
    • 如果方法返回浮点数、长整数或双精度浮点数,则只能使用 freturn、lreturn 或 dreturn 指令,分别对应;
    • 如果方法返回引用类型,则只能使用 areturn 指令。已使用,并且返回值的类型必须与方法的返回描述符在赋值上兼容(JLS 第 5.2 节、第 4.3.3 节)。 4.9 对 Java 虚拟机代码的约束 类文件格式166
  • 所有实例初始化方法、类或接口初始化方法,以及声明为返回 void 的方法,都必须仅使用返回指令。
  • 通过 getfield 指令访问的每个类实例的类型