【Java JVM】Class 文件

news2024/11/6 7:35:48

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.coloradmin.cn/o/1505636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0103n阶行列式-行列式-线性代数

文章目录 一 n阶行列式二 三阶行列式三 特殊行列式结语 一 n阶行列式 ∣ a 11 a 12 ⋯ a 1 n a 21 a 22 ⋯ a 2 n ⋯ ⋯ ⋯ ⋯ a n 1 a n 2 ⋯ a n n ∣ \begin{vmatrix}a_{11}&a_{12}&\cdots&a_{1n}\\a_{21}&a_{22}&\cdots&a_{2n}\\\cdots&\cdots…

大型房企知识竞赛活动方案

(一) 线上挑战赛 1、加入团队 个人可以依据自身情况选择加入初始团队,也可创建团队。 2、题库来源 参考权威题库,适当加入公司帮扶贫困县的相关历史数据题目 3、小程序活动专题页 模块包括:党史知识线上挑战赛、活动宣…

Unity 采用自定义通道ShaderGraph实现FullScreen的窗户雨滴效果

效果如下 ShaderGraph实现 N21 随机化 DragLayer分层 将DragLayer分成四层,分别调整每层的缩放和大小 Shader实现的链接(Unity 雨水滴到屏幕效果) 我也是参考这个实现Shader Graph

Android7.1 ANR error 弹窗处理

Android7.1 ANR error 弹窗处理 问题描述解决方法 郑重声明:本人原创博文,都是实战,均经过实际项目验证出货的 转载请标明出处:攻城狮2015 Platform: Rockchip OS:Android 7.1.2 Kernel: 3.10 问题描述 有时会用到第三方apk,内置到系统中&…

通信-CAN-00 标准概述

总结了下CAN的基本知识,实际CAN的标准,内容,工具使用,上位机开发,下位机开发等,后续会找时间慢慢更新。本文主要介绍CAN标准,并对11898进行了进一步的介绍。 1 CAN概念 CAN-Controller Area N…

C++ 多状态dp

目录 按摩师 打家劫舍 打家劫舍2 删除并获得点数 粉刷房子 按摩师 面试题 17.16. 按摩师 最大值问题 f : 预约此次的最长时间 g :不预约此次的最长时间 出现的错误:return max(f[n - 1]), g[n - 1]); 注意:①题目没给nums的范围&…

软件杯 图像识别-人脸识别与疲劳检测 - python opencv

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是…

【C++】深度解剖多态

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c,Python等 > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:了解什么是多态,熟练掌握多态的定义&a…

预约自习室

预约自习室 1、技术介绍 自习室预约系统的后端开发语言采用Node,后端开发框架采用Express,数据库采用的Node的最佳搭档MySQL。采用Vue作为前端开发框架,Element-UI作为开发的组件库,微信小程序。期间采用axios实现网页数据获取&a…

[java入门到精通] 19 网络编程,设计模式

今日目标 网络编程 TCP通信 Junit单元测试 单例设计模式 多例设计模式 工厂设计模式 1 网络编程 1.1 软件架构 C/S结构 :全称为Client/Server结构,是指客户端和服务器结构。常见程序有QQ、迅雷等软件B/S结构 &#xff1a…

Biomedical knowledge graph-enhanced prompt generation for large language models

1. 生物医学知识图谱增强大语言模型提示生成 论文地址:[2311.17330] Biomedical knowledge graph-enhanced prompt generation for large language models (arxiv.org) 源码地址:https://github.com/BaranziniLab/KG_RAG 2. 摘要 大语言模型&#xff0…

MySQL--索引优化实战篇(1)

前言: 我们常说的SQL优化,简单来说就是索引优化,通过合理创建索引,调整SQL语法等,来提升查询效率,想要进行SQL优化,就必须知道索引的原理,而且能够看懂SQL的执行计划。 MySQL–索引…

基于springboot的校园二手交易平台(程序+数据库+文档)

** 🍅点赞收藏关注 → 私信领取本源代码、数据库🍅 本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路。🍅关注我不迷路🍅** 一、研究背景…

STM32OLED调试工具

OLED介绍 4个引脚的oled : GND引脚接地,VCC接3.3v的电源 SCL与SDA是I2C通信的引脚 使用OLED显示屏驱动函数模块 接线图 将oled函数调试的代码引入到工程项目中 oled工程代码 OLED.C文件代码 #include "stm32f10x.h" #include "OLED_Font…

论文笔记 Where Would I Go Next? Large Language Models as Human Mobility Predictor

arxiv 2023 08的论文 1 intro 1.1 人类流动性的独特性 人类流动性的独特特性在于其固有的规律性、随机性以及复杂的时空依赖性 ——>准确预测人们的行踪变得困难近期的研究利用深度学习模型的时空建模能力实现了更好的预测性能 但准确性仍然不足,且产生的结果…

【Python数据结构与判断1/7】复杂的多向选择

目录 导入 举个栗子 代码优化 elif 栗子 执行顺序 情况一 情况二 情况三 if-elif-else特性 三种判断语句小结 if if-else if-elif-else 嵌套语句 if嵌套 栗子 执行顺序 相互嵌套 Tips Debug 总结 导入 在前面,我们学习了单向选择的if语句和多项…

超越α!PixArt家族新秀PixArt-Σ: 由弱到强训练的文本生成4K图像DiT(华为诺亚)

文章链接: https://arxiv.org/pdf/2403.04692 开源地址:https://pixart-alpha.github.io/PixArt-sigma-project/ PixArt-Σ,这是一个能够直接生成4K分辨率图像的Diffusion Transformer(DiT)。PixArt-Σ相比其前身PixAr…

【unity实战】3D水系统,游泳,潜水,钓鱼功能实现

最终效果 文章目录 最终效果素材将项目升级为URP画一个水潭地形材质升级为URP创建水调节水第一人称人物移动控制游泳水面停留添加水下后处理水下呼吸钓鱼参考完结 素材 https://assetstore.unity.com/packages/vfx/shaders/urp-stylized-water-shader-proto-series-187485 将…

06 数据结构之树

引言&#xff1a; 数的代码实现&#xff0c; 先序遍历、中序、后序、层次遍历 /* binary_tree.h */ #ifndef _BINARY_TREE_H #define _BINARY_TREE_H#include <stdio.h> #include <stdlib.h> #include <string.h>#define DEBUG(msg) \printf("--%s--, %…

【工作】如何写好一份工作自评/总结 述职报告

文章目录 一、述职与工作汇报1、述职是什么&#xff1f;2、述职的目标&#xff08;表扬/体谅/资源&#xff09;3、述职的对象&#xff08;挑战/规划/方法&#xff09; 二、如何做好一份述职报告1、述职内容2、述职PPT制作3、述职试讲练习 三、附工作自评 一、述职与工作汇报 1…