JVM(4):Class文件结构
1. 平台无关性
Java
在刚刚诞生时就有一句非常著名的口号:一次编写,到处运行。这个想法通过虚拟机实现,这些虚拟机都可以执行同一种与平台无关的字节码,从而达到一次编写,到处运行。可以说,字节码就是平台无关性的基石。Java
虚拟机并不与Java
语言绑定,在其之上还可以运行许多其他语言,如Kotlin
、Groovy
和Scala
等。Java
虚拟机唯一绑定的是类似于Class
文件这种特殊的二进制文件,其中包含了Java
虚拟机的指令集、符号表以及其他辅助信息。通过Class
文件,Java
虚拟机并不需要关心来源是什么语言,只要这门语言能够生成可以被读取的Class
文件,那么它就可以在Java
虚拟机上运行。
Java
语言中的各种语法、关键字、常量变量和运算符号的语义最终都会转成字节码指令的组合,这就要求字节码指令的表达能力必须比Java
语言的表达能力更强。从而,一些Java
语言中并不支持的特性,字节码也能够表达出来。
2. Class
类文件
Class
文件是一组以 $8$ 个字节为单位的二进制流,数据项目按照顺序紧凑地排列在文件之中。对于 $8$ 字节以上空间的数据项,会按照高位在前的大端序分割存储在若干个 $8$ 字节中。
Class
文件采取一种类似于C
语言结构体的伪结构来存储数据,包含两种数据类型:
- 无符号数:即基本数据类型,通过 $u1$ 、$u2$ 、$u3$ 、$u4$ 分别表示 $1$ 个字节、$2$ 个字节、$4$ 个字节和 $8$ 个字节的无符号数,用于表示数字、索引引用、数量值或者
UTF-8
编码的字符串; - 表:由多个无符号数或者其他表构成的复合数据类型,一般以 $_-info$ 结尾,用于表示具有层次关系的数据。可以把整个
Class
文件都视为表,按照对应类型的数据紧凑排列而成。
当需要描述类型相同但数量不定的数据时,经常会使用一个前置的容器计数器加若干个连续的数据项的形式,称为该类型的集合。
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$ 常量等。符号引用则属于编译原理方面的概念,包括:
- 被模块导出或者开放的包 ( $Package$ );
- 类和接口的全限定名 ( $Fully\ \ Qualified\ \ Name$ );
- 字段的名称和描述符 ( $Descriptor$ );
- 方法的名称和描述符;
- 方法句柄 ( $Method\ \ Handle$ ) 和方法类型 ( $Method\ \ Type$ );
- 动态调用点 ( $Dynamically-Computed\ \ Call\ \ Site$ ) 和动态常量 ( $Dynamic-Computed\ \ Constant$ )。
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 LocalVariableTable
和LocalVariableTypeTable
$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 SourceFile
和SourceDebugExtension
$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 Deprecated
和Synthetic
$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$ 以键值对的形式表示注解的参数和值。