JVM(4):Class文件结构

1. 平台无关性

        Java在刚刚诞生时就有一句非常著名的口号:一次编写,到处运行。这个想法通过虚拟机实现,这些虚拟机都可以执行同一种与平台无关的字节码,从而达到一次编写,到处运行。可以说,字节码就是平台无关性的基石。Java虚拟机并不与Java语言绑定,在其之上还可以运行许多其他语言,如KotlinGroovyScala等。Java虚拟机唯一绑定的是类似于Class文件这种特殊的二进制文件,其中包含了Java虚拟机的指令集、符号表以及其他辅助信息。通过Class文件,Java虚拟机并不需要关心来源是什么语言,只要这门语言能够生成可以被读取的Class文件,那么它就可以在Java虚拟机上运行。
        Java语言中的各种语法、关键字、常量变量和运算符号的语义最终都会转成字节码指令的组合,这就要求字节码指令的表达能力必须比Java语言的表达能力更强。从而,一些Java语言中并不支持的特性,字节码也能够表达出来。

2. Class类文件

        Class文件是一组以 $8$ 个字节为单位的二进制流,数据项目按照顺序紧凑地排列在文件之中。对于 $8$ 字节以上空间的数据项,会按照高位在前的大端序分割存储在若干个 $8$ 字节中。
        Class文件采取一种类似于C语言结构体的伪结构来存储数据,包含两种数据类型:

        当需要描述类型相同但数量不定的数据时,经常会使用一个前置的容器计数器加若干个连续的数据项的形式,称为该类型的集合。

2.1 魔数和版本号

        Class文件的头 $4$ 字节称为魔数 ( $Magic\ \ Number$ ),用于确定该文件能否被虚拟机接受。魔数在很多文件格式中都存在,文件格式制定者可以自定义魔数,只要不会引起混淆。Class文件的魔数为 $0xCAFEBABE$ ,象征着一款咖啡。
        在魔数之后是Class文件的版本号,前两个字节是次版本号 ( $Minor\ \ Version$ ),后两个字节是主版本号 ( $Major\ \ Version$ )。Java主版本号从 $45$ 开始,JDK 1.1之后每个大版本都会在之前的基础上加 $1$ 。次版本号在JDK 1.1及以前曾经短暂使用过,在JDK 1.2之后废弃,全部使用 $0$ 代替,直到JDK 12,由于一些复杂特性需要进行公测,于是重新启用了次版本号。

2.2 常量池

        常量池是Class文件中第一次出现表的数据项目,也是与其他项目关联最多的数据,通常也是最庞大的数据项目之一。常量池入口是一个 $u2$ 类型的数据,是常量池容量计数值 ( $constant_-pool_-count$ ),从 $1$ 开始,$0$ 表示不指向常量池中的数据项目。Class文件中只有常量池的容量计数是从 $1$ 开始的。
        常量池中主要存放字面量 ( $Literal$ ) 和符号引用 ( $Symbolic\ \ References$ )。字面量类似于Java中的常量,存放字符串、$final$ 常量等。符号引用则属于编译原理方面的概念,包括:

        Java代码在进行Javac编译时没有连接步骤,而是在虚拟机加载Class文件时进行动态连接,所以Class文件中不会保存方法和字段在内存中的位置信息,需要虚拟机在运行期进行转换。在类加载时,会从常量池中获取对应的符号引用,并在类创建和运行时解析,并翻译到具体内存地址中。
        常量池的每一个常量都是一个表,包含 $17$ 结构不同的表结构数据,起始都是一个 $u1$ 类型标志位表示常量类型。

类型 标志 描述
$CONSTANT_-Utf8_-info$ $1$ UTF-8编码的字符串
$CONSTANT_-Integer_-info$ $3$ 整型字面量
$CONSTANT_-Float_-info$ $4$ 浮点型字面量
$CONSTANT_-Long_-info$ $5$ 长整型字面量
$CONSTANT_-Double_-info$ $6$ 双精度浮点型字面量
$CONSTANT_-Class_-info$ $7$ 类或接口的符号引用
$CONSTANT_-String_-info$ $8$ 字符串类型字面量
$CONSTANT_-Fieldref_-info$ $9$ 字段的符号引用
$CONSTANT_-Methodref_-info$ $10$ 类中方法的符号引用
$CONSTANT_-InterfaceMethodref_-info$ $11$ 接口中方法的符号引用
$CONSTANT_-NameAndType_-info$ $12$ 字段或方法的部分符号引用
$CONSTANT_-MethodHandle_-info$ $15$ 方法句柄
$CONSTANT_-MethodType_-info$ $16$ 方法类型
$CONSTANT_-Dynamic_-info$ $17$ 动态计算常量
$CONSTANT_-InvokeDynamic_-info$ $18$ 动态方法调用点
$CONSTANT_-Module_-info$ $19$ 模块
$CONSTANT_-Package_-info$ $20$ 模块中开放或者导出的包

        起初只提供了前 $11$ 种结构,后续为了更好地支持动态语言,额外增加了 $4$ 种,之后又为了支持Java模块化系统 ( $Jigsaw$ ),又加入了两种。
        我们可以通过 $javap\ \ -verbose$ 命令查看Class文件中常量池的常量。

2.3 访问标志

        在常量池之后是访问标志 ( $access_-flags$ ),由 $2$ 字节组成,用于识别一些类或者接口的访问信息,包括:Class是类还是接口、是否为 $public$ 、是否为 $abstract$ 、是否为 $final$ ( 如果是类 ) 等等。

标志 标志值 描述
$ACC_-PUBLIC$ $0x0001$ 是否为 $public$
$ACC_-FINAL$ $0x0010$ 是否为 $final$
$ACC_-SUPER$ $0x0020$ 是否允许使用 $invokespecial$ 字节码指令的新语义,在JDK 1.0.2之后都设置为真
$ACC_-INTERFACE$ $0x0200$ 表示接口
$ACC_-ABSTRACT$ $0x400$ 是否为 $abstract$
$ACC_-SYNTHETIC$ $0x1000$ 表示该类并非由用户代码产生
$ACC_-ANNOTATION$ $0x2000$ 表示注解
$ACC_-ENUM$ $0x4000$ 表示枚举
$ACC_-MODULE$ $0x8000$ 表示模块

2.4 索引

        类索引 ( $this_-class$ ) 和父类索引 ( $super_-class$ ) 都是 $u2$ 类型的数据,均指向一个 $CONSTANT_-Class_-info$ 的类描述符常量;接口索引集合 ( $interfaces$ ) 是一组 $u2$ 类型的数据的集合,第一项 $u2$ 为接口计数器 ( $interfaces_-count$ ),表示索引表容量,后面的每一项均指向一个 $CONSTANT_-Class_-info$ 的类描述符常量。 Class文件通过这三项数据确定继承关系。类索引确定类的全限定名,父类索引确定类的父类的全限定名。除了 $Java.lang.Object$ 之外,所有的类都具有父类,而在 $Object$ 中,父类索引为 $0$ 。接口索引集合用于表示类实现的接口,按照声明顺序从左到右存储。

2.5 字段表集合

        字段表 ( $Field_-info$ ) 用于表示接口和类中声明的变量。

类型 名称 数量
$u2$ $access_-flags$ $1$
$u2$ $name_-index$ $1$
$u2$ $descriptor_-index$ $1$
$u2$ $attributes_-count$ $1$
$attribute_-info$ $attributes$ $attributes_-count$
名称 标志值 描述
$ACC_-PUBLIC$ $0x0001$ 是否为 $public$
$ACC_-PRIVATE$ $0x0002$ 是否为 $private$
$ACC_-PROTECTED$ $0x0004$ 是否为 $protected$
$ACC_-STATIC$ $0x0008$ 是否为 $static$
$ACC_-FINAL$ $0x0010$ 是否为 $final$
$ACC_-VOLATILE$ $0x0040$ 是否为 $volatile$
$ACC_-TRANSIENT$ $0x0080$ 是否为 $transient$
$ACC_-SYNTHETIC$ $0x1000$ 是否为编译器自动产生
$ACC_-ENUM$ $0x4000$ 是否为 $enum$

        接口当中的字段必须有 $ACC_-PUBLIC$ 、$ACC_-STATIC$ 、$ACC_-FINAL$ 标志。
        $name_-index$ 和 $descriptor_-index$ 在标志值之后,是常量池的引用,表示字段简单名称以及字段和方法的描述符。在描述符中,基本数据类型以及 $void$ 使用一个大写字母表示,对象类型使用 $L$ 加全限定名表示。

标识字符 描述
$B$ $byte$
$C$ $char$
$D$ $double$
$F$ $float$
$I$ $int$
$J$ $long$
$S$ $short$
$Z$ $boolean$
$V$ $void$
$L$ 对象类型

        对于数组类型,每一个维度都会使用一个前置的 $[$ 字符表示,例如 $int[\ ][\ ]$ 会表示为 $[[I$ 。对于方法,会按照参数、返回值的方式描述,参数按照顺序放在 $(\ )$ 之中,例如 $int\ \ indexOf(char[]\ \ source,\ \ int\ \ index)$ 表示为 $([CI)I$ 。
        $descriptor_-index$ 之后是属性表计数器,当字段存在额外信息如值时,计数器会记录字段的额外信息个数,并可以在之后的 $attribute_-info$ 中查询。字段表集合不会列出从父类或者父接口中继承而来的字段,但有可能出现原来Java代码之中不存在的字段,譬如内部类中包含的指向外部类实例的字段。

2.6 方法表集合

        Class文件中对字段和方法的描述采取了几乎一样的方式,同样包括 $access_-flags$、$name_-index$、$descriptor_-index$ 和 $attributes$ 。

名称 标志值 描述
$ACC_-PUBLIC$ $0x0001$ 是否为 $PUBLIC$
$ACC_-PRIVATE$ $0x0002$ 是否为 $PRIVATE$
$ACC_-PROTECTED$ $0x0004$ 是否为 $protected$
$ACC_-STATIC$ $0x0008$ 是否为 $static$
$ACC_-FINAL$ $0x0010$ 是否为 $final$
$ACC_-SYNCHRONIZED$ $0x0020$ 是否为 $synchronized$
$ACC_-BRIDGE$ $0x0040$ 是否为编译器产生的桥接方法
$ACC_-VARARGS$ $0x0080$ 是否接受变长参数
$ACC_-NATIVE$ $0x0100$ 是否为 $native$
$ACC_-ABSTRACT$ $0x0400$ 是否为 $abstract$
$ACC_-STRICT$ $0x0800$ 是否为 $strictfp$
$ACC_-SYNTHETIC$ $0x1000$ 是否由编译器自动产生

        方法块内的Java代码,经由Javac编译后,存放在方法属性表集合中名为 $Code$ 的属性内。Java语言重载要求简单名称相同之外,还要拥有一个与原方法不同的特征签名。特征签名包括方法名称、参数顺序及参数类型。也就是说,仅仅改变返回值并不能算重载。但是Class文件中只要描述符不是完全一致,那么就允许多个方法共存,也即允许除了返回值之外其他都相同的方法共存。

2.7 属性表集合

        属性表 ( $attribute_-info$ ) 在前面已经出现过,Class文件、字段表、方法表都可以包含一个属性表集合。属性表集合对顺序没有严格要求,而且可以在属性名不重复的前提下写入自定义的属性信息。Java虚拟机在运行时会忽略不认识的属性信息。对于每一个属性,它的名称都要从常量池中引用一个 $CONSTANT_-Utf8_-info$ 类型的常量表示。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u1$ $info$ $attribute_-length$

        属性值的结构是完全自定义的,只需要通过一个 $u4$ 类型的属性说明其占用位数即可。

2.7.1 Code

        $Code$属性存储方法体内的经过Javac编译后产生的字节码指令。接口方法和抽象方法中不存在该属性。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $max_-stack$ $1$
$u2$ $max_-locals$ $1$
$u4$ $code_-length$ $1$
$u1$ $code$ $code_-length$
$u2$ $exception_-table_-length$ $1$
$exception_-info$ $exception_-table$ $exception_-table_-length$
$u2$ $attributes_-count$ $1$
$attribute_-info$ $attributes$ $attributes_-count$

        $attribute_-name_-index$ 指向 $CONSTANT_-Utf8_-info$ ,固定为 $Code$ 。$attribute_-length$ 表示属性值长度,固定为属性长度减去本身以及上一个属性的长度即 $6$ 个字节。$max_-stack$ 为操作数栈 ( $Operand\ \ Stack$ ) 深度的最大值,虚拟机根据这个值分配栈帧 ( $Stack\ \ Frame$ ) 中的操作栈深度。$max_-locals$ 表示局部变量所需的空间,单位为变量槽 ( $Slot$ ),即虚拟机为局部变量分配内存的最小单位。对于 $32$ 位的数据类型,会使用一个槽;而对于 $64$ 位的数据类型 $double$ 和 $long$ ,则会使用两个槽。方法参数(包括实例方法中的隐藏参数 $this$ )、异常处理参数、局部变量都要依赖于局部变量表,Javac编译器会根据每个变量的生命周期和类型计算出 $max_-locals$ 的大小。$code_-length$ 和 $code$ 存储字节码指令,每个指令使用 $u1$ 类型的数据大小存储。
        如果方法中存在异常处理块的话,那么异常处理表会跟在字节码指令之后。

类型 名称 数量
$u2$ $start_-pc$ $1$
$u2$ $end_-pc$ $1$
$u2$ $handler_-pc$ $1$
$u2$ $catch_-type$ $1$

        $start_-pc$ 和 $end_-pc$ 表示出现异常的代码行,$handler_-pc$ 为处理异常的行,当 $catch_-type$ 为 $0$ 时,任何异常都要跳转到 $handler_-pc$ 进行处理。

2.7.2 Exceptions

        $Exceptions$ 属性列举出方法中可能抛出的受查异常 ( $Checked\ \ Exceptions$ ),也就是方法声明时在 $throws$ 关键字后面列举的异常。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $number_-of_-exceptions$ $1$
$u2$ $exception_-index_-table$ $number_-of_-exceptions$

        $number_-of_-exceptions$ 表示可能抛出的 $number_-of_-exceptions$ 种异常,每种异常使用一个 $exception_-index_-table$ 表示,指向 $CONSTANT_-Class_-info$ 型常量的索引,代表异常类型。

2.7.3 LineNumberTable

        $LineNumberTable$ 用于描述Java源码行号与字节码行号之间的对应关系,并非运行时的必须属性,可以在Javac中使用 $-g:none$ 或者 $-g:lines$ 来取消或者强制生成该信息。如果取消生成,那么当程序抛出异常时,不会显示出错的行号,并且在调试时也无法在源码处设置断点。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $line_-number_-table_-length$ $1$
$line_-number_-info$ $line_-number_-table$ $line_-number_-table_-length$

        $line_-number_-table$ 是一个数量为 $line_-number_-table_-length$ ,类型为 $line_-number_-info$ 的集合。$line_-number_-info$ 包含 $start_pc$ 和 $line_-number$ 两个 $u2$ 类型的数据,前者为字节码行号,后者为源码行号。

2.7.4 LocalVariableTableLocalVariableTypeTable

        $LocalVariableTable$ 描述栈帧中局部变量表与Java源码中变量的关系,同样并非运行时的必须属性,可以在Javac中使用 $-g:none$ 或者 $-g:vars$ 来取消或者强制生成该信息。如果取消生成,那么当其他人引用该方法时,所有参数名称都会丢失,IDE会使用譬如 $arg0$ 这样的占位符代替参数名。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $local_-variable_-table_-length$ $1$
$local_-variable_-info$ $local_-variable_-table$ $local_-variable_-table_-length$

        其中 $local_-variable_-info$ 的结构如下:

类型 名称 数量
$u2$ $start_-pc$ $1$
$u2$ $length$ $1$
$u2$ $name_-index$ $1$
$u2$ $descriptor_-index$ $1$
$u2$ $index$ $1$

        $start_-pc$ 和 $length$ 分别代表局部变量生命周期开始的字节码行数以及作用域长度;$name_-index$ 和 $descriptor_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量,分别代表局部变量的名称以及描述符;$index$ 存储局部变量在栈帧的局部变量表中变量槽的起始位置。
        $LocalVariableTypeTable$ 类似于 $LocalVariableTable$ ,但是将 $descriptor_-index$ 替换为特征签名 ( $Signature$ )。对于非泛型类来说,描述符和特征签名相同。但是对于泛型类,由于擦除的存在,需要特征签名来对泛型进行描述。

2.7.5 SourceFileSourceDebugExtension

        $SourceFile$ 用于记录生成Class文件的源码文件名称,同样是可选的,可以在Javac中使用 $-g:none$ 或者 $-g:source$ 来取消或者强制生成信息。对于大多数类来说,类名和文件名一致,但是对于特殊情况如内部类,如果取消生成,那么在抛出异常时不会显示出错代码所在的文件名。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $sourcefile_-index$ $1$

        $sourcefile_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量索引,常量值为源码文件的文件名。
        JDK 5中加入的 $SourceDebugExtension$ 属性存储额外的代码调试信息,为非Java语言编写的,但需要编译成字节码在虚拟机中运行的程序,比如JSP等,提供了一个进行调试的标准机制。通过SourceDebugExtension,可以快速从异常堆栈中定位出JSP中出现问题的行号。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u1$ $debug_-extension[attribute_-length]$ $1$

        $debug_-extension$ 存储的就是额外的调试信息,通过一组变长的UTF-8格式的字符串。一个类最多只允许一个 $SourceDebugExtension$ 属性。

2.7.6 ConstantValue

        $ConstantValue$ 用于通知虚拟机自动为静态变量赋值,只有静态变量才能使用该属性。对于非静态变量,会在实例构造器 <$init$>$(\ )$ 方法中赋值;而静态变量可以通过类构造器 <$clinit$>$(\ )$ 或者 $ConstantValue$ 进行赋值。Javac编译器会根据变量是否为 $final$ 以及变量类型来决定是否生成 $ConstantValue$ :如果为 $final$ 并且类型为基本类型或者 $String$ ,则生成。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $constantvalue_-index$ $1$

        $constantvalue_-index$ 指向常量池中一个字面量常量的引用,可以是 $CONSTANT_-Long_-info$ 、$CONSTANT_-Integer_-info$ 等。

2.7.7 InnerClasses

        $InnerClasses$ 用于记录内部类与宿主类之间的关联。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $number_-of_-classes$ $1$
$inner_-classes_-info$ $inner_-class$ $number_-of_-classes$

        $inner_-classes_-info$ 的结构如下:

类型 名称 数量
$u2$ $inner_-class_-info_-index$ $1$
$u2$ $outer_-class_-info_-index$ $1$
$u2$ $inner_-name_-index$ $1$
$u2$ $inner_-class_-access_flags$ $1$

        $inner_-class_-info_-index$ 和 $outer_-class_-info_-index$ 都指向 $CONSTANT_-Class_-info$ 类型的常量的索引,分别为内部类和宿主类的符号引用。$inner_-name_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量的索引,表示内部类的名称 (匿名内部类值为 $0$ )。

名称 标志值 描述
$ACC_-PUBLIC$ $0x0001$ 是否为 $public$
$ACC_-PRIVATE$ $0x0002$ 是否为 $private$
$ACC_-PROTECTED$ $0x0004$ 是否为 $protected$
$ACC_-STATIC$ $0x0008$ 是否为 $static$
$ACC_-FINAL$ $0x0010$ 是否为 $final$
$ACC_-INTERFACE$ $0x0020$ 是否为接口
$ACC_-ABSTRACT$ $0x0400$ 是否为抽象类
$ACC_-SYNTHETIC$ $0x1000$ 是否为编译器自动生成
$ACC_-ANNOTATION$ $0x2000$ 是否为注解
$ACC_-ENUM$ $0x4000$ 是否为枚举

2.7.8 DeprecatedSynthetic

        $Deprecated$ 和 $Synthetic$ 都是布尔值,分别表示是否废弃和是否由编译器自动产生。后者也可以通过设置标志位 $ACC_-SYNTHETIC$ 代替。当它们存在时,即代表为真。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$

        其中 $attribute_-length$ 的值恒为 $0x00000000$ 。

2.7.9 StackMapTable

        $StackMapTable$ 在JDK 6之后加入Class文件结构中,位于 $Code$ 属性的属性表中,在虚拟机类加载的字节码验证阶段被新类型检查验证器 ( $Type\ \ Checker$ ) 使用。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $number_-of_-entries$ $1$
$stack_-map_-frame$ $stack_-map_-frame_-entries$ $number_-of_-entries$

        JDK 7后,如果 $Code$ 中没有 $StackMapTable$ ,那么他就具有一个隐式的 $StackMapTable$ 属性,$number_-of_-entries$ 值为 $0$ 。

2.7.10 Signature

        $Signature$ 是一个可选的定长属性,可以出现在类、字段表和方法表结构的属性表中,记录泛型签名信息。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $signature_-index$ $1$

        $signature_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量的索引,表示类签名或者方法类型或者字段类型签名。

2.7.11 BootstrapMethod

        $BootstrapMethod$ 在JDK 7之后添加到Class文件结构中,存储 $invokedynamic$ 指令引用的引导方法限定符。如果某个Class文件常量池中出现过 $CONSTANT_-InvokeDynamic_-info$ 类型的常量,那么这个文件属性表中必须存在一个 $BootstrapMethod$ 属性,且最多只能有一个。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $num_-bootstrap_-methods$ $1$
$bootstrap_-method$ $bootstrap_-methods$ $num_-bootstrap_-methods$

        $bootstrap_-method$ 结构如下:

类型 名称 数量
$u2$ $bootstrap_-method_-ref$ $1$
$u2$ $num_-bootstrap_-arguments$ $1$
$u2$ $bootstrap_-arguments$ $num_-bootstrap_-arguments$

        $bootstrap_-method_-ref$ 指向 $CONSTANT_-MethodHandle$ 结构的索引值,代表一个引导方法。

2.7.12 MethodParameters

        $MethodParameters$ 在JDK 8之后加入到Class文件结构中,用于记录变长参数中各个形参的名称和信息。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u1$ $parameters_-count$ $1$
$parameter$ $parameters$ $parameters_-count$

        其中 $parameter$ 结构如下:

类型 名称 数量
$u2$ $name_-index$ $1$
$u2$ $access_-flags$ $1$

        $name_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量的索引,表示参数名称。$access_-flags$ 是参数的状态指示器。

名称 标志值 描述
$ACC_-FINAL$ $0x0010$ 参数被 $final$ 修饰
$ACC_-SYNTHETIC$ $0x1000$ 参数是编译器自动生成的
$ACC_-MANDATED$ $0x8000$ 参数是在源文件中隐式定义的(如 $this$ )

2.7.13 模块化相关属性

        JDK 9中添加了Java的模块化功能,因为模块描述文件 ( $module_-info.java$ ) 最终要编译为Class文件,所以Class文件结构中添加了 $Module$ 、$ModulePackages$ 和 $ModuleMainClass$ 三个属性。
        $Module$ 属性是一个复杂的变长属性。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $module_-name_-index$ $1$
$u2$ $module_-flags$ $1$
$u2$ $module_-version_-index$ $1$
$u2$ $requires_-count$ $1$
$require$ $requires$ $requires_-count$
$u2$ $exports_-count$ $1$
$export$ $exports$ $exports_-count$
$u2$ $opens_-count$ $1$
$open$ $opens$ $opens_-count$
$u2$ $uses_-count$ $1$
$use$ $uses_-index$ $uses_-count$
$u2$ $provides_-count$ $1$
$provide$ $provides$ $provides_-count$

        $module_-flags$ 是模块状态指示器。

名称 标志值 描述
$ACC_-OPEN$ $0x0020$ 模块开放
$ACC_-SYNTHETIC$ $0x1000$ 模块是编译器自动生成的
$ACC_-MANDATED$ $0x8000$ 模块是在源文件中隐式定义的

        $module_-versions_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量的索引,表示模块版本号。
        $ModulePackages$ 用于描述模块中所有的包。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $package_-count$ $1$
$u2$ $package_-index$ $package_-count$

        $package_-index$ 指向 $CONSTANT_-Package_-info$ 类型的常量的索引,代表模块中的一个包。
        $ModuleMainClass$ 用于确定模块的主类。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $main_-class_-index$ $1$

        $main_-class_-index$ 指向 $CONSTANT_-Class_-info$ 类型的常量的索引,代表模块的主类。

2.7.14 运行时注解相关属性

        Class文件在JDK 5之后增加了 $RuntimeVisibleAnnotation$ 、$RuntimeInvisibleAnnotation$ 、$RuntimeVisibleParameterAnnotations$ 和 $RuntimeInvisibleParameterAnnotations$ 四个属性,用于支持注解。JDK 8之后,又添加了 $RuntimeVisibleTypeAnnotations$ 和 $RuntimeInvisibleTypeAnnotations$ 两个属性。
        $RuntimeVisibleAnnotations$ 记录了类、字段或方法声明上的运行时可见注解,可以通过反射获取这个属性。

类型 名称 数量
$u2$ $attribute_-name_-index$ $1$
$u4$ $attribute_-length$ $1$
$u2$ $num_-annotations$ $1$
$annotation$ $annotations$ $num_-annotations$

        其中 $annotation$ 的结构如下:

类型 名称 数量
$u2$ $Type_-index$ $1$
$u2$ $num_-element_-value_-pairs$ $1$
$element_-value_-pair$ $element_-value_-pairs$ $num_-element_-value_-pairs$

        $Type_-index$ 指向 $CONSTANT_-Utf8_-info$ 类型的常量的索引,以字段描述符的形式表示一个注解;$element_-value_-pair$ 以键值对的形式表示注解的参数和值。

JVM(4):Class文件结构