【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.mzph.cn/news/725864.shtml

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

相关文章

11.WEB渗透测试-Linux系统管理、安全加固(上)

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于: 易锦网校会员专享课 上一个内容:10.WEB渗透测试-Linux基础知识-Linux用户权限管理(下)-CSDN博客 进…

【论文笔记】Language Models are Few-Shot Learners

Language Models are Few-Shot Learners 本部分是 GPT-3 技术报告的第一部分:论文正文、部分附录。 后续还有第二部分:GPT-3 的广泛影响、剩下的附录。 以及第三部分(自己感兴趣的):GPT-3 的数据集重叠性研究。 回顾…

部署运维 防火墙,进程 常用命令

防火墙: 1. 查看是否安装了firewalld sudo systemctl status firewalld 查看防火墙状态或者sudo systemctl is-active firewalld 查看防火墙是否是开启状态 2. 开放6379port sudo firewall-cmd --add-port6379/tcp --permanent 刷新防火墙 sudo firewall-cmd --reload 3…

产品展示型wordpress外贸网站模板

孕婴产品wordpress外贸网站模板 吸奶器、待产包、孕妇枕头、护理垫、纸尿裤、孕妇装、孕婴产品wordpress外贸网站模板。 https://www.jianzhanpress.com/?p4112 床品毛巾wordpress独立站模板 床单、被套、毛巾、抱枕、靠垫、围巾、布艺、枕头、乳胶枕、四件套、浴巾wordpre…

职场中的团队合作与个人成长

在职场中,团队合作和个人成长是两个不可或缺的要素。一个优秀的团队可以带来更高的工作效率和更好的业绩,而个人的成长则是职场成功的关键。本文将探讨如何在职场中实现团队合作与个人成长的平衡。 一、团队合作的重要性 在职场中,团队合作是…

【Ubuntu】将多个python文件打包为.so文件

1.为什么要将python打包为.so文件? 保护源码 2.实战例子 a.安装相应的包 pip install cython 验证安装是否成功 cython --version b.实战的文件目录和内容 hi.py # This is a sample Python script.# Press ShiftF10 to execute it or replace it with your…

线性代数 --- 特征值与特征向量

特征值与特征向量 已知任意向量x,现有矩阵A对x进行操作后,得到新的向量Ax。这就好比是自变量x与函数f(x)的关系一样,向量x通过类似“函数”的处理得到了一个新的向量Ax。这个新的向量可能和原向量x方向相同,也可能不同(事实上大多…

HUAWEI华为MateBook D 14 2022款 12代酷睿版集显(NbF-16)工厂模式原厂Windows11预装OEM系统,含F10智能还原功能

系统下载链接:https://pan.baidu.com/s/1kgJvwSq5eOdme7J9PDXIxQ?pwdxvz8 提取码:xvz8 华为笔记本电脑原装出厂系统工厂安装包,含F10功能、系统自带所有驱动、系统属性专属联机支持标志、Office办公软件、华为电脑管家等预装程序&#xf…

刷怪日记-01背包理论问题(二维矩阵实现)

01背包理论问题(二维矩阵实现) 题目 解题思路 确定dp数组以及下标的含义 本题采用二维数组进行解题,那么dp[i] [j]表示从下标0-i的物品任意选择,放进容量为j背包,价值总合最大是多少 i 表示物品第几个物品,j 表示背包容量大小 …

ORACLE 如何使用dblink实现跨库访问

dbLink是简称,全称是databaselink。database link是定义一个数据库到另一个数据库的路径的对象,database link允许你查询远程表及执行远程程序。在任何分布式环境里,database都是必要的。另外要注意的是database link是单向的连接。在创建dat…

LeetCode刷题---填充每个节点的下一个右侧节点指针 II

题解:LeetCode题解 解题思想: 通过创建辅助指针来解决该问题 当当前节点cur不为空的时候,创建辅助节点dummy,使其一直作为每一层的第一个节点的前一个节点。 创建辅助指针pre,pre初始指向dummy,之后通过pre来填充当前层…

2.2 评估方法 机器学习

我们若有一个包含m个样例的数据集,若我们既需要训练,也需要测试,我们该如何处理呢?下面是几种方法: 2.2.1 留出法 “留出法”直接将数据集D划分为两个互斥的集合,其中一个作为训练集S,另一个作…

双指针算法(1)

目录 283.移动零 一、题目描述 二、思路解析 三、代码 1089.复写零 一、题目描述 二、思路解析 三、代码 202.快乐数 一、题目描述 二、思路解析 三、代码 11.盛水最多的容器 一、题目描述 二、思路解析 三、代码 283.移动零 一、题目描述 OJ题目链接&#xf…

python基于django的药品进销存管理系统elsb2

本系统是通过面向对象的python语言搭建系统框架,通过关系型数据库MySQL存储数据。使用django框架进行药店药品的信息管理,用户只需要通过浏览器访问系统即可获取药店药品信息,并可以在线管理,实现了信息的科学管理与查询统计。本文…

OpenAI 大声朗读出来

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

【MySQL】not in遇上null的坑

今天遇到一个问题: 1、当 in 内的字段包含 null 的时候,正常过滤; 2、当 not in 内的字段包含 null 的时候,不能正常过滤,即使满足条件,最终结果也为 空。 测试如下: select * from emp e;当…

2024全国水科技大会之国际合作与新技术、新产品推介会

召集人:中华环保联合会水环境治理专委会 此论坛主要为筛选新技术、新产品,入选企业后续可参与到我会7月的国际考察中,可为企业推荐国际项目合作! 一、会议背景 为积极应对“十四五”期间我国生态环境治理面临的挑战,加…

【nodejs】“__dirname is not defined”错误修复

▒ 目录 ▒ 🛫 问题描述环境 1️⃣ 原理CommonJS vs ESM错误原因 2️⃣ 禁用 ESM 模式并改用 CommonJS方案一:项目方案二:单文件 3️⃣ 在 ESM 模式下自实现__dirname📖 参考资料 🛫 问题 描述 从网上找了一份代码&am…

x86 Ubuntu上编译eudev给龙芯loongarch64架构主机使用

1、下载eudev库eudev-master.zip,链接:eudev库官方地址 2、下载龙芯的交叉编译工具:loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.2.tar.xz,链接:龙芯交叉编译官方地址 3、交叉编译器环境搭建 (1)、…

InfluxDB SHOW SERIES语句按照什么顺序返回?

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 引言样例SHOW SERIES比较原理结论结束语 引言 influxdb的计算引擎为了做到自底而上的…