【Java JVM】Class 文件

Java 的口号 “一次编写, 到处运行 (Write Once, Run Anywhere)” 的基础: JVM 和 所有平台都统一支持的程序存储格式 – 字节码 (Byte Code)。
只要在对应的平台安装对应的 JVM, 将我们编写的源码编译为 Class 文件, 就能达到了一次编写, 导出运行的目标, 中间的所有细节由不同平台的 JVM 进行处理即可。

这个过程中最重要的产物就是 Class 文件, 本文将简单地介绍一下 Class 文件的结构和内容。

1 Java 文件到 Class 文件

我们编写的 Java 源代码需要借助 Javac 编译器, 才能编译成 JVM 能识别的字节码文件, 流程如图所示

Alt 'Java 源代码到 Class 文件'

Javac 是一种编译器, 能将一种语言规范转化成另外一种语言规范, 通常编译器都是将便于人理解的语言规范转化成机器容易理解的语言规范,
如 C/C++ 或者汇编语言都是将源代码直接编译成目标机器码, 这个目标机器代码是 CPU 直接执行的指令集合, 这些指令集合也就是底层的一种语言规范。

Javac 的编译器也是将 Java 这种对人非常友好的编程语言编译成对所有机器都非常友好的一种语言, 这种语言不是针对某种机器或某个平台。
怎么消除不同种类, 不同平台之间的差异这个任务就由 JVM 来完成, 而 Javac 的任务就是将 Java 源代码语言转化为 JVM 能够识别的一种语言,
然后由 JVM 再转化成当前这个机器能够识别的机器语言。

回归到 Java, Javac 的任务就是将 Java 源代码编译成 Java 字节码, 也就是JVM能够识别的二进制代码, 从表面看是将 .java 文件转化为 .class 文件。
而实际上是将 Java 源代码转化成一连串二进制数字, 这些二进制数字是有格式的, 只有 JVM 能够真确的识别他们到底代表什么意思。

编译器把一种语言规范转化为另一种语言规范的这个过程需要哪些步骤? 回答这个问题需要参照《编译原理》, 总结过程如下:

  1. 词法分析:读取源代码, 一个字节一个字节的读进来, 找出这些词法中我们定义的语言关键词如:if、else、while 等, 识别哪些 if
    是合法的哪些是不合法的。这个步骤就是词法分析过程。

词法分析的结果:就是从源代码中找出了一些规范化的 token 流, 就像人类语言中, 给你一句话你要分辨出哪些是一个词语, 哪些是标点符号, 哪些是动词, 哪些是名词。

  1. 语法分析:就是对词法分析中得到的 token 流进行语法分析, 这一步就是检查这些关键词组合在一起是不是符合 Java 语言规范。如 if
    的后面是不是紧跟着一个布尔型判断表达式。

语法分析的结果:就是形成一个符合 Java 语言规定的抽象语法树, 抽象语法树是一个结构化的语法表达形式, 它的作用是把语言的主要词法用一个结构化的形式组织在一起。
这棵语法树可以被后面按照新的规则再重新组织。

  1. 语义分析:语法分析完成之后也就不存在语法问题了, 语义分析的主要工作就是把一些难懂的, 复杂的语法转化成更简单的语法。就如难懂的文言文转化为大家都懂的百话文,
    或者是注释一下一些不懂的成语。

语义分析结果:就是将复杂的语法转化为简单的语法, 对应到 Java 就是将 foreach 转化为 for
循环, 还有一些注释等。最后生成一棵抽象的语法树, 这棵语法树也就更接近目标语言的语法规则。

  1. 字节码生成:将会根据经过注释的抽象语法树生成字节码, 也就是将一个数据结构转化为另外一个数据结构。就像将所有的中文词语翻译成英文单词后按照英文语法组装成英文语句。
    代码生成器的结果就是生成符合 Java 虚拟机规范的字节码。

这个过程中的需要的组件如下图所示

Alt '编译器编译需要的组件'

从上面的描述中我们知道编译就是将一种语言通过分析分解, 再按照一定的方式先形成一个简单的框架 (将 Java 源文件的字节流转化为对应的 token 流),
然后在通过详细的分析按照一定的规定在这个框架里添加东西使这个 token 流形成更加结构化的语法树 (就是将前面生成的token流中的一个个单词组装成一句话),
但是这棵树离我们的目标 – Java 字节码还有点差距。

所以再进行语义分析使那棵粗糙的树更加完整完善 (给类添加默认的构造函数, 检查变量在使用前有没有初始化, 检查操作变量类型是否匹配),
然后 Javac 编译器调用 com.sun.tools.javac.jvm.Gen 类遍历这棵语法树将 Java 方法中的代码块转换成符合 JVM 语法的命令形式的二进制数据。
按照 JVM 的文件组织格式将字节码输出到以 class 为扩展名的文件中, 也就是生成最终的 Java 字节码。

词法分析: 将关键词组织成 token 流即检查源码中的的关键词是否正确并组织成 token 流。
语法分析: 检查源码是否符合 Java 语法规范并将词组成语句。
语义分析: 简化复杂的语法, 检查变量类型是否合法。
代码生成器: 遍历这棵树生成符合 JVM 规范的代码。

2 Class 文件的结构

2.1 Class 文件的特点

任何一个 Class 文件都对应着唯一的一个类或接口的定义信息 (在 JDK9 以后, Java 开始模块化, 出现了 package-info.java, moudle-info.java 这些属于反例, 完全属于描述性的),
但是类或接口并不一定都得定义在文件里 (比如类或接口可以动态生成, 直接送入类加载器中)。

Class 文件是一组以 8 个字节为基础单位的二进制流, 各个数据项目严格按照顺序紧凑地排列在文件之中, 中间没有添加任何分隔符, 也就是整个文件都是必须的内容。
当遇到需要占用 8 个字节以上空间的数据项时, 则会按照高位在前的方式分割成若干个 8 个字节进行存储。

在计算机系统中, 我们是以字节为单位的, 每个地址单元都对应着一个字节, 一个字节为 8 bit。 存储 8 个字节的内容, 没有问题。
但是需要存储超过 8 个字节的时候, 比如 16 字节, 32 字节 时, 怎么办呢?

这时理所当然的在用几个 8 字节进行凑就可以了, 比如 16 个字节用 2 个 8 字节凑, 就满足了。 但是将这 16 个字节分成 2 个 8 字节进行存储时, 出现了 2 种方式的存放方式。
big-endian (数据的低位保存在内存的高地址中, 而数据的高位保存在内存的低地址中) 和 little-endian (数据的低位保存在内存的低地址中, 而数据的高位保存在内存的高地址中)。

比如我们现在有一个 16 Bit 的数据 0x1234, 需要 2 个 8 字节的位置进行, 完全按照内容顺序存储,
12 存储在内存的前面, 也就是高位, 34 存储在后面, 也就是低位, 内存中存储的顺序为 1234, 这个是 little-endion。
但是反着过来, 将 34 存储在内存的前面, 即高位, 12 反而存在内存的后面, 即低位, 存储存储顺序变为 3412, 这个就是 big-endian。

根据 《Java虚拟机规范》 的规定, Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储数据, 这种伪结构中只有两种数据类型: “无符号数” 和 “表”。

无符号数属于基本的数据类型, 以 u1/u2/u4/u8 来分别代表 1 个字节 / 2 个字节 / 4 个字节 / 8 个字节的无符号数, 无符号数可以用来描述数字,
索引引用, 数量值或者按照 UTF-8 编码构成字符串值。

表由多个无符号数或者其他表作为数据项构成的复合数据类型, 为了便于区分, 所有表的命名都习惯性地以 “_info” 结尾。
表用于描述有层次关系的复合结构的数据, 整个 Class 文件本质上也可以视作是一张表, 这种表的内容大致如下:

名称类型数量
magicu41
minor_versionu21
major_versionu21
constant_pool_countu21
constant_poolcp_infoconstant_pool_count - 1
access_flagsu21
this_classu21
super_classu21
interfaces_countu21
interfacesu2interfaces_count
fields_countu21
fieldsfiled_infofields_count
methods_countu21
methodsmethod_infomethods_count
attributes_countu21
attributesattribute_infoattributes_count

下面逐个看一下它们都是什么含义吧。

2.1.1 magic/minor_version/major_version

每个 Class 文件的头 4 个字节被称为魔数 (magic_version), 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件。
Class 文件的魔数为 “0xCAFEBABE”。

接着下面的 4 个字节 (minor_version + major_version), 第 5, 6 位表示次版本号, 7, 8 表示主版本号。

用 JDK8 和 JDK14 编译同一个 Java 文件, 他们的版本号字节分别为 0000 00340000 003a。 这个版本号会影响到 JVM 能否执行这个文件。
每个 JDK 都有自己支持支持的版本号范围, 在这个范围的前提下, 能够向下兼容低版本的, 但是一定不能执行超过自己支持的高版本号的文件。

2.1.2 常量池

和常量池相关的 constant_pool_countconstant_pool
constant_pool_count 这个容量计数是从 1 开始, 而不是 0, 如果常量池数量的值为 22, 这就是代表常量池中有 21 项。

常量池中主要存放两大类常量: 字面量 (Literal) 和符号引用 (Symbolic References)。

字面量比较接近于 Java 语言层面的常量概念, 如文本字符串, 被声明为 final 的常量值等。而符号引用则属于编译原理方面的概念,
主要包括下面几类常量

  1. 被模块导出或者开放的包 (Package)
  2. 类和接口的全限定名 (Fully Qualified Name)
  3. 字段的名称和描述符 (Descriptor)
  4. 方法的名称和描述符
  5. 方法句柄和方法类型 (Method Handle, Method Type, Invoke Dynamic)
  6. 动态调用点和动态常量 (Dynamically-Computed Call Site, Dynamically-Computed Constant)

Java 代码在进行 Javac 编译的时候, 在 Class 文件中不会保存各个方法, 字段最终在内存中的布局信息。
这些字段, 方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址, 也就无法直接被虚拟机使用的。
当虚拟机做类加载时, 将会从常量池获得对应的符号引用, 再在类创建时或运行时解析, 翻译到具体的内存地址之中。

常量池中每一项常量都是一个表, 截至 JDK13, 常量表中分别有 17 种不同类型的常量。
这 17 类表起始的第一位是 u1 类型的标志位 (一般叫做 tag), 代表着当前常量属于什么类型的常量类型。

类型描述标志
CONSTANT_Utf8_infoUTF-8 编码的字符串1
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

字符串表 CONSTANT_Utf8_info 的内容如下

名称类型数量
tagu11
lengthu21
bytesu1length

在 Class 文件, 经过了魔数, 版本, 常量池的数量, 紧接着的就是具体的常量项。
接着往后找,

假设找到第一个字节, 值为 1, 这表示第一个常量为字符串,
接着往下找第 2, 3 个字节 (字符串常量的长度用的是 16 个字节表示), 得到这个字符串常量的长度占了多少个字节,
再往后找对应的字节数, 这个就是字符串的内容了

这样就能得到第一个常量的内容。

一个小知识: u2 类型能表达的最大值 65535, 也就是 64 KB, 如果我们定义了一个字符串常量/方法名等超过了这个临界值, 会编译失败

类或接口的符号引用表 CONSTANT_Class_info 的内容如下

名称类型数量
tagu11
name_indexu21

tag 同上, 而 name_index 是常量池的索引值, 它指向常量池中一个 CONSTANT_Utf8_info 类型常量, 此常量代表了这个类 (或者接口) 的全限定名。
其他类型的常量表差不多, 就不列举了, 在实际中, 我们可以通过各种工具, 对 Class 文件进行分析, 不需要通过如此计算每个字节,
各种转换, 最接近的工具就是 JDK 自带的 javac

2.1.3 访问符标志

经过常量池后, 紧接着的是 u2 表示的访问符标识 (access_flags), 这个标志用于识别一些类或者接口层次的访问信息,
包括: 这个 Class 是类还是接口 / 是否定义为 public 类型 / 是否定义为 abstract 类型 / 如果是类的话, 是否被声明为 final 等

标志名称标志值含义
ACC_PUBLIC0x0001是否为 public 类型
ACC_FINAL0x0010是否被声明为 final, 只有类可设置
ACC_SUPER0x0020是否允许使用 invokespecial 字节码指令的新语义, invokespecial 指令在 JDK1.0.2 发生了改变, 所以为了不混淆, JDK1.0.2 以后这个必须为 true
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400标识这是一个 abstract 的类型, 抽象类或接口这个为 true, 其他为 false
ACC_SYNTHETIC0x1000标识这个类是否由用户代码生成
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举
ACC_MODULE0x8000标识这是一个模板

access_flags 中一共有 16 个标志位可以使用, 当前只定义了其中 9 个, 其他没用到的都为 0 (2 个字节, 16 位, 所以上限为 16 个标识)。

2.1.4 类索引、父类索引与接口索引集合

一个 Java 类的继承 / 实现关系的确定是由下面 4 个参数决定的 this_class, super_class, interface_count, *
interfaces*

this_class: 类索引, 用于确定这个类的全限定名
super_class: 父类索引, 用于确定这个类的父类的全限定名, 除了 java.lang.Object 外, 所有 Java 类的父类索引都不为空
interface_count: 接口个数, 用于确定这个类的接口个数
interfaces: 接口索引集合, 用来描述这个类实现了哪些接口, 按照 implements 关键字从左到右排列 (当然如果当前是一个接口类, 那么就是 extends 关键字的后面)

名称类型数量
this_classu21
super_classu21
interfaces_countu21
interfacesu2interfaces_count
2.1.5 字段表集合

字段表 (field_info) 用于描述接口或者类中声明的变量。

在 Java 中一个字段定义, 会涉及下面几个方面

作用域 (public, private, protected)
实例变量还是类变量 (static)
可变性 (final)
并发可见性 (volatile)
可否被序列化 (transient)
字段数据类型 (基本类型, 对象, 数组)
字段名称

字段表的结构

名称类型数量
acc_flagsu21
name_indexu21
descriptor_indexu21
attributes_countu21
attributesattributes_infoattributes_count

字段修饰符放在 access_flags 项目中, 它与类中的 access_flags 项目是非常类似的, 都是一个 u2 的数据类型, 其中可以设置的标志位和含义。

标志名称标志值含义
ACC_PUBLIC0x0001字段是否为 public 类型
ACC_PRIVATE0x0002字段是否为 private 类型
ACC_PROTECTED0x0004字段是否为 protected 类型
ACC_STATIC0x0008字段是否为 static
ACC_FINAL0x0010字段是否为 final
ACC_VOLATILE0x0040字段是否为 volatile
ACC_TRANSIENT0x0080字段是否 transient
ACC_SYNTHETIC0x1000字段是否由编译器自动产生
ACC_ENUM0x4000字段是否为 enum

很明显, 由于语法规则的约束, ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED 三个标志最多只能选择其一, ACC_FINAL, ACC_VOLATILE 不能同时选择。
接口之中的字段必须有 ACC_PUBLIC, ACC_STATIC, ACC_FINAL 标志, 这些都是由 Java 本身的语言规则所导致的。

在 access_flags 标志的是两项索引值

name_index
descriptor_index

它们都是对常量池项的引用, 分别代表着字段的简单名称以及字段和方法的描述符。

全限定名: 就是包名 + 类名的字符串, 将里面的 . 替换为 /, 同时在末尾加上 ; 表示结束的
简单名称: 就是没有任何类型和参数修饰的方法或字段
描述符: 用来描述字段的数据类型, 方法的参数列表 (包括数量, 类型以及顺序) 和返回值

描述符标识字符含义

标识字符含义
B基础数据类型 byte
C基础数据类型 char
D基础数据类型 double
F基础数据类型 float
I基础数据类型 int
L基础数据类型 long
S基础数据类型 short
Z基础数据类型 boolean
V特殊类型 void
L对象类型 如 Ljava/lang/Object

对于数组类型, 每一维度将使用一个前置的 “[” 字符来描述,
如一个定义为 “java.lang.String[][]” 类型的二维数组将被记录成 “[[Ljava/lang/String;”, 一个整型数组"int[]" 将被记录成 “[I”。

用描述符来描述方法时, 按照先参数列表, 后返回值的顺序描述, 参数列表按照参数的严格顺序放在一组小括号 “()” 之内,
比如方法 “int indexOf(char[]source, int sourceOffset, int sourceCount, char[]target, int targetOffset, int targetCount, int fromIndex)” 的描述符为 “([CII[CIII])I”,
再如 “String toString(String str)” 的描述符为 “(Ljava/lang/String;)Ljava/lang/String;”

在描述符后面的就是属性表集合了, 用于存储一些额外的信息。
字段表可以在属性表中附加描述零至多项的额外信息, 比如默认值之类的, 具体内容后面的属性表在聊。

字段表集合中不会列出从父类或者父接口中继承而来的字段, 但有可能出现原本 Java 代码之中不存在的字段,
譬如在内部类中为了保持对外部类的访问性, 编译器就会自动添加指向外部类实例的字段。

2.1.6 方法表集合

Class 文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式, 方法表的结构如同字段表一样,
依次包括访问标志 (access_flags), 名称索引 (name_index), 描述符索引 (descriptor_index), 属性表集合 (attributes) 几项

方法表的结构

名称类型数量
acc_flagsu21
name_indexu21
descriptor_indexu21
attributes_countu21
attributesattributes_infoattributes_count

因为 volatile 关键字和 transient 关键字不能修饰方法, 所以方法表的访问标志中没有了 ACC_VOLATILE 标志和 ACC_TRANSIENT 标志。
与之相对, synchronized, native, strictfp 和 abstract 关键字可以修饰方法, 方法表的访问标志中也相应地增加了
ACC_SYNCHRONIZED, ACC_NATIVE、ACC_STRICTFP 和 ACC_ABSTRACT 标志

标志名称标志值含义
ACC_PUBLIC0x0001是否为 public 方法
ACC_PRIVATE0x0002是否为 private 方法
ACC_PROTECTED0x0004是否为 protected 方法
ACC_STATIC0x0008是否为 static 方法
ACC_FINAL0x0010是否为 final 方法
ACC_SYNCHRONIZED0x0020是否为 synchronized 方法
ACC_BRIDGE0x0040是否为编译器生成的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x0100是否为 native 方法
ACC_ABSTRACT0x0400是否为 abstract 方法
ACC_STRICT0x0800是否为 strictfp 方法
ACC_SYNTHETIC0x1000方法是否为编译器自动产生
2.1.7 属性表集合

属性表 (attribute_info) 与 Class 文件中其他的数据项目要求严格的顺序, 长度和内容不同, 不再要求各个属性表具有严格顺序。

属性表的结构

名称类型数量
attribute_name_indexu21
attribute_lengthu11
infou1attribute_length

虚拟机规范预定义的属性

属性名称使用位置含义
Code方法表Java 代码编译成的字节码指令
ConstantValue字段表由 final 关键字定义的常量值
Deprecated类, 方法表, 字段表被声明为 deprecated 的类, 方法, 字段
Exceptions方法表方法抛出的异常列表
EnclosingMethod类文件仅当一个类为局部类或匿名类时才能拥有这个属性, 这个属性用于标志这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode 属性Java 源码的行号和字节码指令的对应关系
LocalVariableTableCode 属性方法的局部变量描述
StackMapTableCode 属性供新的类型检查验证器 (Type Checker) 检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature类, 方法表, 字段表用于支持泛型下的方法签名
SourceFile类文件记录源文件名称
SourceDebugExtension类文件用于存储额外的调试信息
Synthetic类, 方法表, 字段表标识方法或字段是编译器自动生成的
LocalVariableTypeTable使用特征签名替代描述符, 是为了引入泛型语法后能描述泛型参数化类型
RuntimeVisibleAnnotations类, 方法表, 字段表为动态注解提供支持, 用于指明哪些注解是运行时可见的
RuntimeInVisibleAnnotations类, 方法表, 字段表为动态注解提供支持, 用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations方法表作用和 RuntimeVisibleAnnotations 相似, 只不过这个只能用于方法参数
RuntimeInVisibleParameterAnnotations方法表作用和 RuntimeInVisibleAnnotations 相似, 只不过这个只能用于方法参数
AnnotationDefault方法表用于记录注解类元素的默认值
BoostrapMethods类文件用于保存 invokedynamic 指令引用的引导方法限定符
RuntimeVisibleTypeAnnotations类, 方法表, 字段表, Code 属性为实现 JSR 308 中新增的类型注解提供的支持, 用于指明哪些注解是运行时可见的
RuntimeInVisibleTypeAnnotations类, 方法表, 字段表, Code 属性为实现 JSR 308 中新增的类型注解提供的支持, 用于指明哪些注解是运行时不可见的
MethodParameters方法表用于支持 (编译时加入 -parameters 参数) 将方法名称编译进 Class 文件, 并可运行时获取
Module用于记录一个 Module 的名称和相关的信息 (requires, exports, opens, uses, provides)
ModulePackages用于记录一个模块中所有被 exports 或 opens 的包
ModuleMainClass用于指定一个模块的主类
NestHost用于支持嵌套类的反射和访问控制 API, 一个内部类通过该属性得知自己的宿主类
NestMembers用于支持嵌套类的反射和访问控制 API, 一个内部类通过该属性得知自己有哪些内部类
  • Code 属性

Code 属性表的结构

名称类型数量
attribute_name_indexu21
attribute_lengthu41
max_stacku21
max_localsu21
code_lengthu41
codeu1cide_length
exception_tableu21
attributes_countu21
attributesattributes_infoattributes_count

attribute_name_index 是一项指向 CONSTANT_Utf8_info 型常量的索引, 此常量值固定为 “Code”, 它代表了该属性的属性名称,
attribute_length 指示了属性值的长度。

max_stack 代表了操作数栈 (Operand Stack) 深度的最大值。 在方法执行的任意时刻, 操作数栈都不会超过这个深度。
虚拟机运行的时候需要根据这个值来分配栈帧 (Stack Frame) 中的操作栈深度

max_locals 代表了局部变量表所需的存储空间。 在这里, max_locals 的单位是变量槽 (Slot), 变量槽是虚拟机为局部变量分配内存所使用的最小单位。
对于 byte, char, float, int, short, boolean 和 returnAddress 等长度不超过 32 位的数据类型, 每个局部变量占用一个变量槽, 而
double 和 long 这两种 64 位的数据类型则需要两个变量槽来存放。 方法参数 (包括实例方法中隐藏参数 “this”),
显式异常处理程序的参数 (Exception Handler Parameter, 就是 try-catch 语句中 catch 块中所定义的异常),
方法体中定义的局部变量都需要依赖局部变量表来存放。 并不是在方法中用了多少个局部变量, 就把这些局部变量所占变量槽数量之和作为
max_locals 的值, 在代码实际运行中, 槽有时可以重用。

code_length 和 code 用来存储 Java 源程序编译后生成的字节码指令。 code_length 代表字节码长度, code
是用于存储字节码指令的一系列字节流。
注: 虽然 code_length 的类型为 u4, 理论上最大值可以达到 2 的 32 次幂, 但是 《Java虚拟机规范》 中明确限制了一个方法不允许超过
65535 条字节码指令, 即它实际只使用了 u2 的长度。

code 属性是 Class 文件中最重要的一个属性, 用于描述代码。

exception_table 异常表 (此处的异常表不是上面的 Exception) 对于 Code 属性来说并不是必须存在的, 如果出现的话, 它的结构如下

名称类型数量
start_pcu21
end_pcu21
handler_pcu21
catch_typeu21

第 start_pc 行开始 到 end_pc (不包含 end_pc) 行出现了 catch_type 类型或其子类的异常, 跳转到 handler_pc 行继续执行,
catch_type 为 0, 则任意异常到需要到 handler_pc 行去处理。

  • Exception 属性

此处的异常不是上面的异常表。 Exceptions 属性的作用是列举出方法中可能抛出的受查异常 (Checked Exceptions), 也就是方法描述时在
throws 关键字后面列举的异常

Exception 属性结构

名称类型数量
attribute_name_indexu21
attribute_lengthu41
number_of_exceptionsu21
exception_index_tableu21

number_of_exceptions 项表示方法可能抛出 number_of_exceptions 种受查异常, 每一种受查异常使用一个 exception_index_table
项表示; exception_index_table 是一个指向常量池中 CONSTANT_Class_info 型常量的索引。

至于属性表集合其他的属性, 这里就不一一列举了。
有兴趣可以阅读一下 周志明的《深入理解Java虚拟机》第 3 部分。

参考

《深入理解Java虚拟机》- 周志明
Java代码编译过程简述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/434996.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务基础

目录 一、单体架构 二、分布式架构 三、微服务 四、微服务结构 五、SpringCloud 六、服务拆分 七、远程调用 一、单体架构 单体架构就是将业务的所有功能都集中在一个项目中进行开发,并打成一个包进行部署。 他的优点很明显,就是架构简单&#xff…

微信小程序(五十二)开屏页面效果

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.使用控件模拟开屏界面 2.倒计时逻辑 3.布局方法 4.TabBar隐藏复现 源码&#xff1a; components/openPage/openPage.wxml <view class"openPage-box"><image src"{{imagePath}}"…

单细胞联合BulkRNA分析思路|加个MR锦上添花,增强验证~

今天给大家分享一篇IF7.3的单细胞MR的文章&#xff0c;2023年12月发表在Frontiers in Immunology&#xff1a;An integrative analysis of single-cell and bulk transcriptome and bidirectional mendelian randomization analysis identified C1Q as a novel stimulated risk…

力扣刷题Days11第二题--141. 环形链表(js)

目录 1,题目 2&#xff0c;代码 2.1快慢指针 2.2&#xff0c;哈希表 3&#xff0c;学习与总结 3.1自己尝试写快慢指针 反思 1,题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&…

【视频转码】基于RK3588的视频转码探索

传统的视频转码服务基本都是基于X86下CPU、GPU转码&#xff0c;对硬件性能、功耗、成本来说都比较高。从技术角度来说现有视频转码技术有&#xff1a; 视频编码转变&#xff1a; 1. H.264 > H.265 保持视频分辨率、清晰度不变情况下&#xff0c;更改视频压缩方式&#xff0…

hyperf 二十五 数据迁移 一

教程&#xff1a;Hyperf 版本说明 一 生成迁移 php bin/hyperf.php gen:migration create_users_table 执行文件&#xff1a;Hyperf\Database\Commands\Migrations\GenMigrateCommand 功能&#xff1a;创建迁移文件 参数&#xff1a; name 文件名称 选项&#xff1a; c…

【JS】关于this的使用

this 前言一、this是什么&#xff1f;二、做什么&#xff1f;1.全局环境2.函数环境3.new实例对象4.apply、bind、call绑定4.1 apply()4.2 call()4.3 bind() 三、为什么用this&#xff1f;四、如何改变this&#xff1f;五、应用场景&#xff1f;总结 前言 痛点 经常写Vue项目&a…

day36 贪心算法part5

435. 无重叠区间 中等 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 气球问题稍加改动就可ac 一个交叉区间里&#xff0c;最终只能保留一个&#xff0c;其他的全部要去掉。…

软考66-上午题-【面向对象技术】-小结+杂题

一、杂题 真题1&#xff1a; 真题2&#xff1a; 真题4&#xff1a; 真题5&#xff1a; 真题6&#xff1a; 二、面向对象设计-总结 2-1、考题分析 选择题&#xff1a;11道&#xff08;11分&#xff09; 综合分析题&#xff1a;2道&#xff08;30分&#xff09; java程序设计…

Common Sense Machines(CSM):立志成为图像生成适用于游戏引擎的3D资产AI产品

详细说明 Common Sense Machines&#xff08;CMS&#xff09;&#xff1a;立志成为图像生成适用于游戏引擎的3D资产AI产品-喜好儿aigc详细说明&#xff1a;https://heehel.com/CSM-3d 官方网站&#xff1a;https://www.csm.ai/ 使用体验网址&#xff1a;https://3d.csm.ai/ 来…

Rust错误处理和Result枚举类异常错误传递

Rust 有一套独特的处理异常情况的机制&#xff0c;它并不像其它语言中的 try 机制那样简单。 首先&#xff0c;程序中一般会出现两种错误&#xff1a;可恢复错误和不可恢复错误。 可恢复错误的典型案例是文件访问错误&#xff0c;如果访问一个文件失败&#xff0c;有可能是因…

微信小程序用户登陆和获取用户信息功能实现

官方文档&#xff1a; https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html 接口说明&#xff1a; https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html 我们看官方这个图&#xff0c;梳理一下用户…

【Python爬虫实战】抓取省市级城市常务会议内容

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

Three.js--》探寻Cannon.js构建震撼的3D物理交互体验(二)

我们用three.js可以绘制出各种酷炫的画面&#xff0c;但是当我们想要一个更加真实的物理效果的话&#xff0c;这个时候我们就需要一个物理的库&#xff0c;接下来我们就讲解一下今天要学习的canon&#xff0c;它可以给我们提供一个更加真实的物理效果&#xff0c;像物体的张力、…

Python - Pycharm 配置 autopep8 并设置快捷键

什么是 PEP8 官方&#xff1a;PEP 8 – Style Guide for Python Code | peps.python.org PEP8 是 Python 官方推出的一套编码的规范&#xff0c;只要代码不符合它的规范&#xff0c;就会有相应的提示&#xff0c;还可以让代码自动的格式化 Pycharm 自带的代码格式化 ​ 但这…

【C++】String常用的函数总结

目录 一、string的构造函数方式&#xff1a; 二、常用的大小/容量相关操作&#xff1a; 三、string的常用修改操作&#xff1a; 四、string的遍历&#xff1a; 五、string的任意位置插入 / 删除&#xff1a; 六&#xff1a;补充&#xff1a; 一、string的构造函数方式&a…

JavaWeb环境配置 IDE2022版

一、新建一个javaweb文件 文件名可以自己随意改 二、给建立的项目添加框架支持 勾选Web Application,点击确定 建立成功界面&#xff0c;会生成一个新的web文件夹 三、配置tomcat 1、两种打开配置文件方式&#xff1a; 第一种 第二种 2、打开后&#xff0c;点击号&#xf…

LLM | Gemma的初体验

一起来体验一下吧~ google/gemma-7b-it Hugging Face 此型号卡对应于 Gemma 型号的 7B 指令版本。还可以选择 2B 基本模型、7B 基本模型和 2B 指导模型的模型卡。 微调 使用 QLoRA 对 UltraChat 数据集执行监督微调 &#xff08;SFT&#xff09; 的脚本在 TPU 设备上使用 FS…

鸿蒙Harmony应用开发—ArkTS声明式开发(手势处理:绑定手势方法)

为组件绑定不同类型的手势事件&#xff0c;并设置事件的响应方法。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 绑定手势识别 通过如下属性给组件绑定手势识别&#xff0c;手势识别成功后可以通过事…

LVS负载均衡集群基础概念

目录 一、集群 1、集群概述 1.1 什么是集群 1.2 集群系统扩展方式 1.2.1 Scale UP&#xff08;纵向扩展&#xff09; 1.2.2 Scale OUT&#xff08;横向扩展&#xff09; 1.2.3 区别 1.3 分布式系统 1.4 分布式与集群 1.5 集群设计原则 1.6 集群设计实现 1.6.1 基础…