Appearance
第2章 Java虚拟机的结构
提示
来自deepseek解释
原文链接:https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-2.html
第2章 Java虚拟机的结构
本文档定义了一台抽象机器。它并不描述Java虚拟机的任何具体实现。
要正确实现Java虚拟机,只需要能够读取class文件格式并正确执行其中规定的操作即可。不属于Java虚拟机规范部分的实现细节,不应不必要地限制实现者的创造力。例如,运行时数据区的内存布局、所使用的垃圾回收算法,以及Java虚拟机指令的任何内部优化(例如,将其翻译为机器码),都由实现者自行决定。
本规范中所有对Unicode的引用均依据《Unicode标准13.0版》,参见https://www.unicode.org/。
2.1 class文件格式
Java虚拟机执行的编译代码使用一种与硬件和操作系统无关的二进制格式表示,通常(但不一定)存储在一个文件中,这种格式被称为class文件格式。class文件格式精确定义了类或接口的表示,包括诸如字节顺序等细节,这些细节在特定于平台的目标文件格式中可能被视为理所当然。
第4章“class文件格式”详细介绍了class文件格式。
2.2 数据类型
与Java编程语言一样,Java虚拟机操作两种类型:原始类型和引用类型。相应地,存在两种可以在变量中存储、作为参数传递、由方法返回和进行操作的值:原始值和引用值。
Java虚拟机期望几乎所有类型检查都在运行时之前完成,通常由编译器完成,而不必由Java虚拟机本身完成。原始类型的值不需要被打上标签或以其他方式在运行时检查以确定其类型,也不必与引用类型的值区分开来。相反,Java虚拟机的指令集通过使用针对特定类型值进行操作的指令来区分其操作数类型。例如,iadd、ladd、fadd和dadd都是Java虚拟机指令,它们将两个数值相加并产生数值结果,但每条指令都专门针对其操作数类型:分别是int、long、float和double。关于Java虚拟机指令集中类型支持的摘要,请参见§2.11.1。
Java虚拟机包含对对象的显式支持。对象要么是动态分配的类实例,要么是数组。对对象的引用被认为具有Java虚拟机类型reference。reference类型的值可以看作是指向对象的指针。一个对象可能存在多个引用。对象总是通过reference类型的值进行操作、传递和测试。
2.3 原始类型与取值
Java虚拟机支持的原始数据类型包括数值类型、boolean类型(§2.3.4)和**returnAddress类型**(§2.3.3)。
数值类型包括整数类型(§2.3.1)和浮点类型(§2.3.2)。
整数类型包括:
byte,其值为8位有符号二进制补码整数,默认值为零short,其值为16位有符号二进制补码整数,默认值为零int,其值为32位有符号二进制补码整数,默认值为零long,其值为64位有符号二进制补码整数,默认值为零char,其值为16位无符号整数,表示基本多语言平面中的Unicode码点,使用UTF-16编码,默认值为空码点('\u0000')
浮点类型包括:
float,其值精确对应32位IEEE 754 binary32格式可表示的值,默认值为正零double,其值精确对应64位IEEE 754 binary64格式的值,默认值为正零
boolean类型的值编码真值true和false,默认值为false。
《Java虚拟机规范》第一版并未将
boolean视为Java虚拟机类型。但是,boolean值在Java虚拟机中确实有有限的支持。第二版通过将boolean视为一种类型来澄清了这个问题。
returnAddress类型的值是指向Java虚拟机指令操作码的指针。在原始类型中,只有returnAddress类型不与任何Java编程语言类型直接关联。
2.3.1 整数类型与取值
Java虚拟机整数类型的取值如下:
byte:从 –128 到 127(–2⁷ 到 2⁷–1),包含端点short:从 –32768 到 32767(–2¹⁵ 到 2¹⁵–1),包含端点int:从 –2147483648 到 2147483647(–2³¹ 到 2³¹–1),包含端点long:从 –9223372036854775808 到 9223372036854775807(–2⁶³ 到 2⁶³–1),包含端点char:从 0 到 65535(0 到 2¹⁶–1),包含端点
2.3.2 浮点类型与取值
浮点类型为float和double,它们分别与IEEE 754标准中32位 binary32 和64位 binary64 浮点格式的值和操作在概念上相关联(JLS §1.7)。在Java SE 15及更高版本中,Java虚拟机使用IEEE 754标准的2019版。在Java SE 15之前,使用1985版。
IEEE 754不仅包括由符号和大小组成的正数和负数,还包括正零和负零、正无穷大和负无穷大,以及特殊的非数字(NaN)值。NaN值用于表示某些无效操作(如零除以零)的结果。float和double类型的NaN常量分别预定义为Float.NaN和Double.NaN。
浮点类型的有限非零值都可以表示为 s · m · 2(e – N + 1) 的形式,其中:
- s 为 +1 或 –1,
- m 为小于 2N 的正整数,
- e 为介于 Emin = –(2K–1 – 2) 和 Emax = 2K–1 – 1 之间的整数,
- N 和 K 是依赖于类型的参数。
某些值可以用多种方式表示。如果 m ≥ 2N–1,则称为规范化的;否则称为次规范的。次规范值是指其绝对值小于最小规范化值绝对值的值。float和double的参数总结在表2.3.2‑A中。
表2.3.2‑A. 浮点参数
| 参数 | float | double |
|---|---|---|
| N | 24 | 53 |
| K | 8 | 11 |
| Emax | +127 | +1023 |
| Emin | –126 | –1022 |
除NaN外,浮点值是有序的。按从小到大排列,它们依次为:负无穷大、负有限非零值、正零和负零、正有限非零值、正无穷大。
IEEE 754允许每种格式有多个不同的NaN值。但是,Java SE平台通常将给定浮点类型的NaN值视为折叠为单个规范值。NaN是无序的,因此如果任一操作数为NaN,数值比较和相等性测试的结果为false。正零和负零比较相等,但其他操作可以区分它们。
2.3.3 returnAddress类型与取值
returnAddress类型由Java虚拟机的jsr、ret和jsr_w指令使用。returnAddress类型的值是指向Java虚拟机指令操作码的指针。与数值原始类型不同,returnAddress类型不对应于任何Java编程语言类型,也不能由正在运行的程序修改。
2.3.4 boolean类型
尽管Java虚拟机定义了boolean类型,但它仅提供非常有限的支持。没有专门用于操作boolean值的Java虚拟机指令。相反,Java编程语言中操作boolean值的表达式被编译为使用Java虚拟机int数据类型的值。
Java虚拟机确实直接支持boolean数组。其newarray指令支持创建boolean数组。boolean类型的数组使用字节数组指令baload和bastore进行访问和修改。在Oracle的Java虚拟机实现中,boolean数组被编码为字节数组,每个元素使用8位,用1表示true,0表示false。
2.4 引用类型与取值
有三种引用类型:类类型、数组类型和接口类型。它们的值分别是对动态创建的类实例、数组或实现接口的类实例或数组的引用。
数组类型由具有单个维度的组件类型组成(其长度不由类型给出)。组件类型本身可以是数组类型。如果从任何数组类型开始,考虑其组件类型,然后(如果那也是数组类型)再考虑该类型的组件类型,依此类推,最终必须达到一个不是数组类型的组件类型;这称为数组类型的元素类型。元素类型必然是原始类型、类类型或接口类型。
引用值也可以是特殊的空引用,即不引用任何对象的引用,此处用null表示。空引用最初没有运行时类型,但可以强制转换为任何类型。引用类型的默认值为null。本规范不强制要求null的具体值编码。
2.5 运行时数据区
Java虚拟机定义了程序执行期间使用的各种运行时数据区。其中一些数据区在Java虚拟机启动时创建,仅在Java虚拟机退出时销毁。其他数据区是线程私有的。线程私有的数据区在线程创建时创建,在线程退出时销毁。
2.5.1 pc寄存器
每个Java虚拟机线程都有自己的pc(程序计数器)寄存器。在任何时候,每个线程都在执行单个方法的代码,即该线程的当前方法(§2.6)。如果该方法不是native的,则pc寄存器包含当前正在执行的Java虚拟机指令的地址。如果当前执行的方法是native的,则pc寄存器的值未定义。pc寄存器足够宽,可以容纳特定平台上的returnAddress或本机指针。
2.5.2 Java虚拟机栈
每个Java虚拟机线程都有一个私有的Java虚拟机栈,与线程同时创建。Java虚拟机栈存储栈帧(§2.6)。它类似于C等传统语言的栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。栈的内存不需要是连续的。本规范允许Java虚拟机栈具有固定大小,或者根据计算需要动态扩展和收缩。
与Java虚拟机栈相关的异常情况如下:
- 如果线程中的计算需要比允许的栈更大,则Java虚拟机抛出
StackOverflowError。 - 如果栈可以动态扩展,但尝试扩展时内存不足,或者如果内存不足无法为新线程创建初始栈,则Java虚拟机抛出
OutOfMemoryError。
2.5.3 堆
Java虚拟机有一个在所有Java虚拟机线程之间共享的堆。堆是运行时数据区,所有类实例和数组的内存都从这里分配。堆在虚拟机启动时创建。对象的堆存储由自动存储管理系统(垃圾收集器)回收;对象从不显式释放。堆可以具有固定大小,也可以根据计算需要扩展或收缩。其内存不需要是连续的。
如果计算需要比自动存储管理系统能够提供的更多堆内存,则Java虚拟机抛出OutOfMemoryError。
2.5.4 方法区
Java虚拟机有一个在所有Java虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码存储区,或类似于操作系统进程中的“文本”段。它存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括用于类和接口初始化以及实例初始化的特殊方法(§2.9)。
方法区在虚拟机启动时创建。虽然它在逻辑上是堆的一部分,但简单的实现可以选择不进行垃圾收集或压缩。它可以是固定大小或可扩展的。如果方法区中的内存无法满足分配请求,则Java虚拟机抛出OutOfMemoryError。
2.5.5 运行时常量池
运行时常量池是类或接口在运行时的constant_pool表(§4.4)的表示。它包含几种常量,从编译时已知的数字字面量到必须在运行时解析的方法和字段引用。它类似于传统编程语言的符号表。
每个运行时常量池都在Java虚拟机的方法区(§2.5.4)中分配。类或接口的运行时常量池在类或接口被Java虚拟机创建(§5.3)时构造。如果构造需要比方法区中可用内存更多的内存,则抛出OutOfMemoryError。
2.5.6 本地方法栈
Java虚拟机的实现可以使用传统的栈(俗称“C栈”)来支持本地方法(用Java编程语言以外的语言编写的方法)。本地方法栈也可以用于以C等语言实现的Java虚拟机指令集解释器。如果提供,它们通常在线程创建时为每个线程分配。它们可以是固定大小或可动态扩展的。
与本地方法栈相关的异常情况如下:
- 如果线程中的计算需要比允许的本地方法栈更大,则抛出
StackOverflowError。 - 如果本地方法栈可以动态扩展但内存不足,或无法为新线程创建初始栈,则抛出
OutOfMemoryError。
2.6 栈帧
栈帧用于存储数据和部分结果,以及执行动态链接、方法返回值和分派异常。每次调用方法时都会创建一个新的栈帧。当方法调用完成时,栈帧被销毁,无论这种完成是正常的还是突然的(它抛出一个未捕获的异常)。栈帧从创建它的线程的Java虚拟机栈中分配。每个栈帧都有自己的局部变量数组(§2.6.1)、自己的操作数栈(§2.6.2)以及对当前方法所属类的运行时常量池(§2.5.5)的引用。
局部变量数组和操作数栈的大小在编译时确定,并与方法关联的代码一起提供(§4.7.3)。在任何给定的控制线程中,只有一个栈帧(正在执行的方法的栈帧)是活动的,称为当前栈帧,其方法为当前方法,当前方法所在的类为当前类。
2.6.1 局部变量
每个栈帧包含一个变量数组,称为其局部变量。数组长度在编译时确定,并在类或接口的二进制表示中与方法关联的代码一起提供(§4.7.3)。
单个局部变量可以保存boolean、byte、char、short、int、float、reference或returnAddress类型的值。一对局部变量可以保存long或double类型的值。局部变量通过索引寻址,第一个局部变量的索引为零。long或double类型的值占用两个连续的局部变量,并使用较小的索引寻址。Java虚拟机不要求该索引为偶数;64位对齐不是强制性的。
Java虚拟机使用局部变量在方法调用时传递参数。对于类(静态)方法调用,参数从局部变量0开始依次在连续的局部变量中传递。对于实例方法调用,局部变量0始终用于传递对正在调用实例方法的对象的引用(Java编程语言中的this)。任何参数随后从局部变量1开始在连续的局部变量中传递。
2.6.2 操作数栈
每个栈帧包含一个后进先出(LIFO)栈,称为其操作数栈。操作数栈的最大深度在编译时确定,并与方法关联的代码一起提供(§4.7.3)。操作数栈在包含它的栈帧创建时为空。Java虚拟机提供指令将常量或来自局部变量或字段的值加载到操作数栈上,其他指令从操作数栈获取操作数,对它们进行操作,并将结果压回操作数栈。
操作数栈上的每个条目可以保存任何Java虚拟机类型的值,包括long和double。值必须以其类型适当的方式操作;例如,不可能压入两个int值然后将其视为long。少数指令(dup指令和swap)以原始值方式操作而不考虑具体类型,但它们被定义得不能修改或拆分单个值。这些限制通过class文件验证(§4.10)强制执行。
在任何时间点,操作数栈都有一个关联的深度,其中long或double类型的值对深度贡献两个单位,其他类型贡献一个单位。
2.6.3 动态链接
每个栈帧包含对当前方法类型的运行时常量池的引用,以支持方法代码的动态链接。方法的class文件代码通过符号引用引用要调用的方法和要访问的变量。动态链接将这些符号引用转换为具体引用,根据需要加载类以解析尚未定义的符号,并将变量访问转换为与这些变量的运行时位置相关联的存储结构中的适当偏移量。这种方法和变量的晚绑定使得方法使用的其他类中的更改不太可能破坏此代码。
2.6.4 方法正常调用完成
如果方法调用没有导致抛出异常,则方法调用正常完成。如果正常完成,则可以向调用方法返回一个值。这发生在被调用方法执行一条返回指令(§2.11.8)时,返回指令的选择必须适合所返回值(如果有)的类型。当前栈帧用于恢复调用者的状态,包括其局部变量和操作数栈,调用者的程序计数器适当增加以跳过方法调用指令。然后执行在调用方法的栈帧中正常继续,返回值(如果有)被压入该栈帧的操作数栈。
2.6.5 方法异常调用完成
如果方法内的Java虚拟机指令执行导致Java虚拟机抛出异常,并且该异常未在方法内处理,则方法调用异常完成。执行athrow指令也会导致显式抛出异常,如果异常未被当前方法捕获,则导致方法调用异常完成。异常完成的方法调用永远不会向调用者返回值。
2.7 对象的表示
Java虚拟机不强制要求对象的任何特定内部结构。在Oracle的某些Java虚拟机实现中,对类实例的引用是指向句柄的指针,该句柄本身是一对指针:一个指向包含对象方法的表以及表示对象类型的Class对象的指针,另一个指向从堆中为对象数据分配的内存的指针。
2.8 浮点运算
Java虚拟机包含了IEEE 754标准(JLS §1.7)中指定的浮点运算的子集。在Java SE 15及更高版本中,使用2019版IEEE 754标准。许多算术(§2.11.3)和类型转换(§2.11.4)指令都处理浮点数。这些指令通常对应于IEEE 754操作,但某些指令如取余指令(drem、frem)在舍入策略上有所不同,取反指令不要求反转NaN操作数的符号位。
Java虚拟机支持的浮点运算与IEEE 754标准之间的主要区别包括:
- 浮点取余指令(
drem、frem)基于使用向零舍入策略的隐含除法,而IEEE 754余数基于向最近舍入。 - 取反指令(
dneg、fneg)不要求反转NaN操作数的符号位。 - 浮点指令不会抛出异常、捕获或以其他方式发出IEEE 754异常条件(无效操作、除零、溢出、下溢或不精确)的信号。
- Java虚拟机不支持IEEE 754信号比较,也没有信号NaN值。
Java虚拟机定义了两种舍入策略:
- 向最近舍入 – 适用于除转换为整数和取余之外的所有浮点指令。不精确结果舍入到最接近的可表示值;如果同样接近,则选择最低有效位为零的那个。这对应于IEEE 754的
roundTiesToEven。 - 向零舍入 – 适用于浮点值到整数值的转换(
d2i、d2l、f2i、f2l)和取余指令(drem、frem)。不精确结果舍入到最接近但幅值不大于精确结果的可表示值。这对应于IEEE 754的roundTowardZero。
在Java SE 17及更高版本中,Java虚拟机要求严格评估浮点表达式。ACC_STRICT标志(在早期版本中使用)不再影响浮点语义。
2.9 特殊方法
2.9.1 实例初始化方法
一个类有零个或多个实例初始化方法,每个通常对应于Java编程语言中编写的构造函数。如果满足以下所有条件,则该方法是一个实例初始化方法:
- 它定义在类中(不是接口)。
- 它具有特殊名称
<init>。 - 它是
void(§4.3.3)。
在类中,任何名为<init>的非void方法不是实例初始化方法。在接口中,任何名为<init>的方法不是实例初始化方法。此类方法不能被任何Java虚拟机指令调用,并会被格式检查拒绝。实例初始化方法只能由invokespecial指令在未初始化的类实例上调用。
2.9.2 类初始化方法
一个类或接口最多有一个类或接口初始化方法,并由Java虚拟机调用该方法进行初始化(§5.5)。如果满足以下所有条件,则该方法是一个类或接口初始化方法:
- 它具有特殊名称
<clinit>。 - 它是
void(§4.3.3)。 - 在版本号为51.0或以上的类文件中,该方法设置了
ACC_STATIC标志且不带参数(§4.6)。
其他名为<clinit>的方法不是类初始化方法;它们永远不会被Java虚拟机本身调用,也不能被任何指令调用。
2.9.3 签名多态方法
如果满足以下所有条件,则一个方法是签名多态的:
- 它声明在
java.lang.invoke.MethodHandle类或java.lang.invoke.VarHandle类中。 - 它只有一个
Object[]类型的形参。 - 它设置了
ACC_VARARGS和ACC_NATIVE标志。
Java虚拟机在invokevirtual指令中对签名多态方法给予特殊处理,以实现方法句柄的调用或对java.lang.invoke.VarHandle实例引用的变量的访问。
2.10 异常
Java虚拟机中的异常由Throwable类或其子类的实例表示。抛出异常会导致从抛出点立即进行非局部控制转移。
大多数异常是同步发生的,作为线程中某个操作的结果。异步异常则可能在程序执行的任何点发生。Java虚拟机因以下三种原因之一抛出异常:
- 执行了
athrow指令。 - Java虚拟机同步检测到异常执行条件(例如数组越界、加载/链接失败、资源限制超出)。
- 由于调用了
Thread或ThreadGroup的stop方法,或Java虚拟机实现内部错误,发生异步异常。
Java虚拟机抛出的异常是精确的:当发生控制转移时,在抛出异常点之前执行的所有指令的效果必须看起来已经发生,而该点之后的所有指令不得看起来已被执行。
每个方法可以关联零个或多个异常处理器。异常处理器指定代码中的偏移范围、它能处理的异常类型以及处理代码的位置。当抛出异常时,Java虚拟机在当前方法中从异常处理表开头开始搜索匹配的处理器。如果找到,控制转移到处理代码;否则当前方法调用异常完成,并在调用者上下文中重新抛出异常。
2.11 指令集概要
Java虚拟机指令由一字节操作码(指定要执行的操作)后跟零个或多个操作数组成。许多指令没有操作数。字节码指令流仅按单字节对齐,除了lookupswitch和tableswitch指令,它们被填充以强制某些操作数在4字节边界上对齐。
2.11.1 类型与Java虚拟机
大多数指令在其操作码助记符中显式编码类型信息:i表示int,l表示long,s表示short,b表示byte,c表示char,f表示float,d表示double,a表示reference。有些指令没有类型字母(例如arraylength、goto)。
由于操作码大小为一字节,指令集并非完全正交。Java虚拟机为某些操作提供了降低级别的类型支持。表2.11.1‑A总结了指令集中的类型支持。
Java虚拟机实际类型与计算类型之间的映射如表2.11.1‑B所示。
(表2.11.1‑A和2.11.1‑B在此省略,它们存在于原始规范中。)
2.11.2 加载与存储指令
加载和存储指令在局部变量和操作数栈之间传输值。包括:
- 加载局部变量:
iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。 - 存储值:
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。 - 加载常量:
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>。 - 使用更宽索引访问更多局部变量:
wide。
2.11.3 算术指令
算术指令从操作数栈取两个值计算结果并压回结果。包括:
- 加法:
iadd、ladd、fadd、dadd - 减法:
isub、lsub、fsub、dsub - 乘法:
imul、lmul、fmul、dmul - 除法:
idiv、ldiv、fdiv、ddiv - 取余:
irem、lrem、frem、drem - 取反:
ineg、lneg、fneg、dneg - 移位:
ishl、ishr、iushr、lshl、lshr、lushr - 按位或:
ior、lor - 按位与:
iand、land - 按位异或:
ixor、lxor - 局部变量自增:
iinc - 比较:
dcmpg、dcmpl、fcmpg、fcmpl、lcmp
整数操作不指示溢出。唯一可能抛出异常的整数操作是除法和取余指令,当除数为零时。浮点指令从不抛出运行时异常;溢出产生无穷大,下溢产生次规范值或零,无效操作产生NaN。
2.11.4 类型转换指令
类型转换指令允许在数值类型之间转换。包括拓宽转换(i2l、i2f、i2d、l2f、l2d、f2d)和缩窄转换(i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f)。拓宽转换不会丢失整体幅值信息,但可能丢失精度(例如int到float)。缩窄转换可能丢失幅值和精度,并可能改变符号;它们从不抛出运行时异常。
2.11.5 对象创建与操作
对象创建和操作指令包括:
- 创建类实例:
new - 创建数组:
newarray、anewarray、multianewarray - 访问字段:
getstatic、putstatic、getfield、putfield - 加载数组组件:
baload、caload、saload、iaload、laload、faload、daload、aaload - 存储到数组:
bastore、castore、sastore、iastore、lastore、fastore、dastore、aastore - 获取数组长度:
arraylength - 检查属性:
instanceof、checkcast
2.11.6 操作数栈管理指令
这些指令直接操作操作数栈:pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2、swap。
2.11.7 控制转移指令
控制转移指令有条件或无条件地使Java虚拟机继续执行除紧随其后的指令之外的指令。包括:
- 条件分支:
ifeq、ifne、iflt、ifle、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmple、if_icmpgt、if_icmpge、if_acmpeq、if_acmpne - 复合条件分支:
tableswitch、lookupswitch - 无条件分支:
goto、goto_w、jsr、jsr_w、ret
2.11.8 方法调用与返回指令
方法调用指令:
invokevirtual– 调用对象的实例方法,根据对象的类型分派。invokeinterface– 调用接口方法。invokespecial– 调用需要特殊处理的实例方法(实例初始化、当前类或超类的方法)。invokestatic– 调用类(静态)方法。invokedynamic– 调用绑定到该指令的调用点对象所指向的方法。
方法返回指令:ireturn(用于boolean、byte、char、short、int)、lreturn、freturn、dreturn、areturn,以及return(用于void)。
2.11.9 抛出异常
异常通过athrow指令以编程方式抛出。各种指令在检测到异常条件时也可以抛出异常。
2.11.10 同步
Java虚拟机使用监视器支持方法和方法内指令序列的同步。方法级同步是隐式执行的:同步方法标记有ACC_SYNCHRONIZED标志,在调用时执行线程进入监视器,调用方法,并在完成(正常或异常)时退出监视器。
指令序列的同步通过monitorenter和monitorexit指令实现,通常用于实现Java编程语言中的synchronized块。正确实现需要编译器的配合。
Java虚拟机允许但不要求强制执行结构化锁定,这保证了在方法调用期间监视器进入和退出的次数相等。
2.12 类库
Java虚拟机必须为Java SE平台类库的实现提供足够的支持。某些类需要Java虚拟机的特殊配合,包括支持反射、类加载、链接和初始化、安全、多线程和弱引用的那些类。
2.13 公开设计,私有实现
本规范概述了Java虚拟机的公共视图:class文件格式和指令集。这些组件对Java虚拟机的硬件、操作系统和实现独立性至关重要。实现者可以在本规范的约束内自由修改或优化实现,只要能够读取class文件格式并保持其代码的语义即可。
实现选项的范围包括在加载时或执行期间将Java虚拟机代码翻译为另一个虚拟机的指令集,或翻译为主机CPU的本机指令集(即时编译)。精确定义的虚拟机和目标文件格式不会显著限制实现者的创造力,许多不同的实现都可以保持兼容性。
注:本译文基于官方《Java虚拟机规范(Java SE 17版)》第2章,根据您提供的PDF内容并参考标准在线版本进行了校正。某些表格和图示已用文本概括;确切的字节级细节请参阅原始规范。