02 Java虚拟机的结构

The Java Virtual Machine Specification, Java SE 8

目录

一、class 类文件格式

二、 数据类型

三、原始类型和值

3.1 基本类型和值

3.2 浮点数类型、值集和值

3.3 returnAddress返回地址的类型和值

3.4 boolean 类型

四、参考类型和值

五、运行时数据区域

5.1 pc寄存器

5.2 Java虚拟机堆栈(Java Virtual Machine Stacks)

5.3 heap堆

5.4 方法区域(Method Area)

5.5 运行时常量池(Run-Time Constant Pool)

5.6 本地方法堆栈(Native Method Stacks)

六、帧(Frames )

6.1 本地变量(Local Variables)

6.2 操作数堆栈(Operand Stacks)

6.3 动态链接(Dynamic Linking)

6.4 正常方法调用完成(Normal Method Invocation Completion)

6.5 突然的方法调用完成(Abrupt Method Invocation Completion)

七、对象的表示(Representation of Objects)

八、 浮点运算;流点数学(Floating-Point Arithmetic)

8.1 Java虚拟机浮点算法和IEEE 754

8.2 浮点模式

8.3 值集转换(Value Set Conversion)

九、特殊方法

十、例外

十一、指令集摘要(Instruction Set Summary)

11.1 类型和Java虚拟机

11.2 加载和存储说明

11.3 算术指令(Arithmetic Instructions)

11.4 类型转换说明(Type Conversion Instructions)

11.5 对象创建和操作(Object Creation and Manipulation)

11.6 操作员堆栈管理说明(Operand Stack Management Instructions)

11.7 控制转移指令(Control Transfer Instructions)

11.8 方法调用和返回指令(Method Invocation and Return Instructions)

11.9 抛出异常

11.10 同步(Synchronization)

十二、 类库(Class Libraries)

十三、公共设计,私有实现(Public Design, Private Implementation)


此文档指定了一个抽象的机器。它没有描述Java虚拟机的任何特定实现。

要正确地实现Java虚拟机,只需要能够读取类文件格式并正确地执行其中指定的操作。不属于Java虚拟机规范的一部分的实现细节将不必要地限制实现者的创造力。例如,运行时数据区域的内存布局、所使用的垃圾收集算法以及Java虚拟机指令的任何内部优化(例如,将它们转换为机器代码)都由实现者自行决定。

本规范中对Unicode的所有引用都是关于Unicode标准,6.0.0版本的,可在http://www.unicode.org/上获得。

一、class 类文件格式

要由Java虚拟机执行的编译代码使用独立于硬件和操作系统的二进制格式表示,通常(但不一定)存储在一个文件中,称为类文件格式。类文件格式精确地定义了类或接口的表示,包括诸如可以使用特定于平台的对象文件格式进行的字节排序这样的细节。

第四章【04 Class类文件格式】,“类文件格式”,详细介绍了类文件格式。

二、 数据类型

与Java编程语言一样,Java虚拟机也可以操作两种类型:原语类型引用类型。相应地,有两种值可以存储在变量中,作为参数传递,通过方法返回,并进行操作:原始值引用值

Java虚拟机期望几乎所有的类型检查都是在运行时之前完成的,通常是由编译器来完成的,而不必由Java虚拟机本身来完成。原始类型的值不需要进行标记或以其他方式进行检查,以在运行时确定其类型,或与引用类型的值区分开来。相反,Java虚拟机的指令集使用旨在操作特定类型的值的指令来区分其操作数类型。例如,iadd、ladd、fadd 和 dadd 都是Java虚拟机指令,它们添加两个数值并产生数值结果,但每个都专门针对其操作数类型:分别为int、long、float 和 double。有关Java虚拟机指令集中的类型支持的摘要,请参见2.11.1(十一、指令集摘要)。

Java虚拟机包含对对象的显式支持。对象可以是动态分配的类实例或数组。对对象的引用被认为具有Java虚拟机类型的引用。类型引用的值可以看作是指向对象的指针。可能存在对一个对象的多个引用。对象总是通过类型引用的值进行操作、传递和测试。

三、原始类型和值

Java虚拟机支持的原始数据类型是数字类型、布尔类型(2.3.4)和返回地址类型(2.3.3)。

数值类型由积分式类型(2.3.1)和浮点式类型(2.3.2)组成。

整体类型为:

  • byte,它的值是8位有符号的二补整数,其默认值为零。
  • short,它的值是16位有符号的二补整数,其默认值为零。
  • int,其值为32位带符号的二补整数,其默认值为零。
  • long,它的值是64位有符号的二补整数,其默认值为。
  • char,其值是16位无符号整数,表示基本多语言平面中的Unicode代码点,用UTF-16编码,其默认值是空代码点(‘\u0000’)。

浮点类型为:

  • float,其值是浮点值集的元素,或者在受支持的情况下,是浮点扩展指数值集,其默认值为正零。
  • double,它的值是双值集的元素,或者在支持的情况下,是双扩展指数值集,其默认值为正零。

boolean 类型的值编码的真值为 true和false,默认值为false。

Java®虚拟机规范的第一版没有认为布尔值是Java虚拟机类型。但是,布尔值在Java虚拟机中的支持确实有限。Java®虚拟机规范的第二版通过将布尔值作为一种类型来澄清了这个问题。

返回地址类型的值是指向Java虚拟机指令的操作码的指针。在原语类型中,只有返回的地址类型与Java编程语言类型没有直接关联。

3.1 基本类型和值

Java虚拟机的基本类型的值为:

  • 对于byte,从-128 到 127 (-27 to 27 - 1),包括边界值
  • 对于short,从-32768 到 32767 (-215 to 215 - 1),包括边界值
  • 对于int,从-2147483648 到 2147483647 (-231 to 231 - 1),包括边界值
  • 对于long,从-9223372036854775808 到 9223372036854775807 (-263 to 263 - 1),包括边界值
  • 对于char,从0 到 65535,包括边界值

3.2 浮点数类型、值集和值

浮点类型是 float 和 double,它们在概念上与32位单精度和64位双精度格式IEEE 754值和IEEE标准(ANSI/IEEEStd。754-1985年,纽约)。

IEEE 754标准不仅包括正和负符号大小数,还包括正和负零、正和负无穷大,以及一个特殊的非数值(以下缩写为“NaN”)。NaN值用于表示某些无效操作的结果,如除以零。

Java虚拟机的每个实现都需要支持两组标准的浮点值,称为浮点值集和双值集。此外,Java虚拟机的实现可以选择支持两个扩展指数浮点值集中的一个或两个,称为浮float扩展指数值集(float-extended-exponent value set)和double扩展指数值集(double-extended-exponent value set)。在某些情况下,可以使用这些扩展指数值集来代替标准值集来表示浮点数或双倍类型的值。

任何浮点值集的有限非零值都可以在形式中表示,s +1或-1,m是一个正整数小于2N,e是一个在之间的整数,包容边界值,N和K取决于该值的参数集。有些值可以以多种形式以这种形式表示;
例如,假设一个值v的值集可能表示在这种形式中使用某些值s,m,e,然后如果发生m甚至e小于2^(K-1),可以减半m和增加1产生第二个表示相同的值v。
如果m≥2^(N-1),则这种形式的表示称为标准化;否则该表示称为去正化
如果一个值集中的一个值不能以m≥2^(N-1)的方式表示,那么该值就被称为一个去规格化的值,因为它没有规范化的表示。

表2.3.2-A总结了两个要求和两个可选浮点值集的参数Emin和Emax)的约束。

Table 2.3.2-A. Floating-point value set parameters

如果一个实现支持一个或两个扩展指数值集,那么对于每个支持的扩展指数值集,都有一个特定的与实现相关的常数K,其值受表2.3.2-A的约束;这个值K依次决定了Emin和Emax的值。

这四个值集中的每一个不仅包括上面赋予它的有限非零值,还包括正零、负零、正无穷、负无穷和NaN这五个值。

请注意,表2.3.2-A中的约束设计使得浮点值集的每个元素必然也一定是浮点扩展指数值集、双值集和双扩展指数值集的一个元素。同样地,双值集的每个元素也必然是双扩展指数值集的一个元素。每个扩展的指数值集都比相应的标准值集有更大的指数值范围,但并没有更高的精度。

浮点值集的元素正是可以使用IEEE 754标准中定义的单一浮点格式表示的值,除了只有一个NaN值(IEEE 754指定了224-2个不同的NaN值)。双值集的元素正是可以使用IEEE 754标准中定义的双浮点格式来表示的值,除了只有一个NaN值(IEEE 754指定了253-2个不同的NaN值)。但是,请注意,这里定义的浮动扩展指数值集和双扩展指数值集的元素并不对应于可以分别使用IEEE 754单扩展和双扩展格式表示的值。本规范没有要求对浮点值集的值进行特定的表示,除非浮点值必须以类文件格式(4.4.4,4.4.5)表示。

浮动、浮动扩展指数、双指数和双扩展指数值集不是类型。Java虚拟机的实现使用float值集的元素来表示float类型的值总是正确的;但是,在某些上下文中,实现可能允许使用浮点扩展指数值集的元素来代替。类似地,实现使用双值集的元素来表示双值类型的值总是正确的;但是,在某些上下文中,实现可能允许使用双扩展指数值集的元素来代替。

除了NaN外,浮点值集的值都是有序的。当从最小到最大排列时,它们分别是负无穷、负有限值、正负零、正有限值和正无穷。

Float正零和Float负零比较相等,但有其他操作可以区分它们;
例如,1.0除以0.0产生正无穷大,而1.0除以-0.0产生负无穷大。

NaN是无序的,所以如果它们的其中一个或两个都是NaN,数值比较和数值相等测试的值是假的。特别地,当且仅当值为NaN时,对值与自身的数值相等的检验值为false。如果任何一个操作数都是NaN,则对数值不等式的检验值为真。

3.3 returnAddress返回地址的类型和值

返回地址类型由Java虚拟机的jsr、ret和jsr_w指令(jsr、ret、jsr_w)使用。返回地址类型的值是指向Java虚拟机指令的操作码的指针。与数字原语类型不同,返回的地址类型不对应于任何Java编程语言类型,并且不能被正在运行的程序修改。

3.4 boolean 类型

虽然Java虚拟机定义了一个布尔类型,但它只提供了非常有限的支持。没有专门用于对布尔值的操作的Java虚拟机指令。相反,操作布尔值的Java编程语言中的表达式被编译以使用Java虚拟机int数据类型的值。

Java虚拟机确实直接支持boolean数组。它的newarray指令允许创建boolean数组。使用byte数组指令来访问和修改布尔类型的数组。

在Oracle的Java虚拟机实现中,Java编程语言中的布尔数组被编码为Java虚拟机字节数组,每个布尔元素使用8位。 

Java虚拟机使用1表示true来编码布尔数组组件,使用0表示false。
当Java编程语言的布尔值被编译器映射到Java虚拟机类型int的值时,编译器必须使用相同的编码。

四、参考类型和值

有三种引用类型:class 类型数组类型接口类型
它们的值分别是对动态创建的类实例数组实现接口的类实例数组的引用

数组类型由具有单一维度的组件类型组成(其长度不由该类型给出)。
数组类型的组件类型本身可以是数组类型。
如果从任何数组类型开始,考虑其组件类型,然后(如果这也是数组类型)该类型的组件类型,以此类推,最终必须达到非数组类型的组件类型;这称为数组类型的元素类型。
数组类型的元素类型必须是基语类型、类类型或接口类型

引用值也可以是特殊的空引用,即对无对象的引用,在这里将用null表示。
空引用最初没有运行时类型,但可以强制转换为任何类型。引用类型的缺省值为空。

此规范并不要求将一个具体的值编码为null。

五、运行时数据区域

Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。
其中一些数据区域是在Java虚拟机启动时创建的,并且只有在Java虚拟机退出时才会被销毁。
其他数据区域是每个线程对应的。每个线程数据区是在线程创建和在线程退出时销毁时创建的。

5.1 pc寄存器

Java虚拟机可以同时支持多个执行线程(JLS 17,Java®Language Specification 17)。
每个Java虚拟机线程都有它自己的pc(程序计数器)寄存器。
在任何时候,每个Java虚拟机线程都在执行一个单一方法的代码,即该线程的当前方法(2.6)。
如果该方法不是本地的,则pc寄存器将包含当前正在执行的Java虚拟机指令的地址。
如果线程当前正在执行的方法是本机的,则Java虚拟机的pc寄存器的值未定义。
Java虚拟机的pc寄存器足够宽,可以在特定的平台上保存一个返回地址或一个本机指针。

5.2 Java虚拟机堆栈(Java Virtual Machine Stacks)

每个Java虚拟机线程都有一个私有的Java虚拟机堆栈,与该线程同时创建。
Java虚拟机堆栈将存储帧(2.6)。
Java虚拟机堆栈类似于C等传统语言的堆栈:它包含局部变量和部分结果,并在方法调用和返回中发挥作用。因为Java虚拟机堆栈除了推送和弹出帧之外,永远不会被直接操作,所以帧可能会被堆分配。Java虚拟机堆栈的内存不需要是连续的。

在Java®虚拟机规范的第一版中,Java虚拟机堆栈被称为Java堆栈。

此规范允许Java虚拟机堆栈具有固定的大小,或者根据计算的需要进行动态扩展和收缩。
如果Java虚拟机堆栈的大小是固定的,那么在创建该堆栈时,可以独立地选择每个Java虚拟机堆栈的大小。

Java虚拟机实现可以为程序员或用户提供对Java虚拟机堆栈的初始大小的控制,以及在动态扩展或收缩Java虚拟机堆栈的情况下,对最大和最小大小的控制。

以下异常情况与Java虚拟机堆栈相关联:

  • 如果线程中的计算需要比允许的更大的Java虚拟机堆栈,则Java虚拟机会抛出一个堆栈溢出错误StackOverflowError。
  • 如果可以动态扩展Java虚拟机栈,并尝试扩展,但可以提供足够的内存来实现扩展,

    或者如果为新线程创建初始Java虚拟机堆栈的内存不足,则Java虚拟机将抛出内存溢出错误OutOfMemoryError。

5.3 heap堆

Java虚拟机有一个在所有Java虚拟机线程之间共享的堆。堆是分配所有类实例和数组的内存。

该堆是在虚拟机启动时创建的。对象的堆存储由自动存储管理系统(称为聚合器)回收;对象不会明确释放。Java虚拟机不包含任何特定类型的自动存储管理系统,并且可以根据实现者的系统要求来选择存储管理技术。堆的大小可以是固定的,也可以根据计算的需要进行扩展,如果更大的堆成为不必要的,则可以收缩。堆的内存不需要是连续的

Java虚拟机实现可以为程序员或用户提供对堆的初始大小的控制,以及如果堆可以动态扩展或收缩,那么还可以提供对堆的最大和最小堆大小的控制。

以下异常条件与堆相关联:

  • 如果计算需要的堆超过自动存储管理系统可用的堆,Java虚拟机会抛出一个内存错误OutOfMemoryError。

5.4 方法区域(Method Area)

Java虚拟机有一个在所有Java虚拟机线程之间共享的方法区域。该方法区域类似于使用传统语言的编译代码的存储区域,或类似于操作系统过程中的“text文本”段。它存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括在类和实例初始化和接口初始化中使用的特殊方法(2.9)。

该方法区域将在虚拟机启动时创建。尽管方法区域在逻辑上是堆的一部分,但简单的实现可能会选择不进行垃圾收集或压缩它。本规范没有规定方法区域的位置或用于管理编译代码的策略。方法区域可以是固定的大小,也可以根据计算的需要进行扩展,如果不需要更大的方法区域,则可以收缩。方法区域的内存不需要是连续的。

Java虚拟机实现可以为程序员或用户提供对方法区域的初始大小的控制,以及在不同大小的方法区域的情况下,对最大和最小方法区域大小的控制。

以下异常条件与方法区域有关:

  • 如果方法区域中的内存不能可用来满足分配请求,则Java虚拟机会抛出一个超出内存错误 OutOfMemoryError。

5.5 运行时常量池(Run-Time Constant Pool)

运行时常量池是类文件(4.4)中的 constant_pool 表的每个类或每个接口的运行时表示形式。
它包含几种常量,从编译时已知的数字文字到必须在运行时解析的方法和字段引用。
运行时常量池提供的函数类似于传统编程语言的符号表的函数,尽管它包含比典型符号表更广泛的数据范围。

每个运行时常量池都是从Java虚拟机的方法区域(2.5.4)中分配的。
一个类或接口的运行时常量池是在由Java虚拟机创建该类或接口(5.3)时构造的。

以下异常条件与类或接口的运行时常量池的构造有关:

  • 在创建类或接口时,如果运行时常量池的构造需要的内存超过Java虚拟机方法区域的内存,那么Java虚拟机会抛出一个内存错误 OutOfMemoryError。

有关运行时常量池的信息,请参阅【第五章,05 加载、链接和初始化】(加载、链接和初始化)。

5.6 本地方法堆栈(Native Method Stacks)

Java虚拟机的实现可以使用传统的堆栈,俗称“C堆栈”,来支持本机方法(用Java编程语言以外的语言编写的方法)。本地方法栈也可以被Java虚拟机指令集解释器的实现使用,例如c语言。
Java虚拟机实现如果不能加载本地方法,并且本身不依赖于传统的栈,就不需要提供本地方法栈。
如果提供本机方法栈,则通常在创建每个线程时为每个线程分配本机方法栈。

此规范允许本地方法堆栈具有固定的大小,或者根据计算的需要进行动态扩展和收缩。
如果本地方法堆栈的大小是固定的,则在创建该堆栈时,可以独立地选择每个本机方法堆栈的大小。

Java虚拟机实现可以为程序员或用户提供对本机方法堆栈的初始大小的控制,以及在不同大小的本机方法堆栈的情况下,提供对最大和最小方法堆栈大小的控制。

以下异常条件与本机方法堆栈相关联:

  • 如果线程中的计算需要比允许的值更大的本机方法堆栈,那么Java虚拟机就会抛出一个堆栈溢出错误StackOverflowError。
  • 如果可以动态扩展本机方法堆栈,并且尝试扩展本机方法堆栈,但内存不足,或者如果内存不足可以为新线程创建初始本机方法堆栈,那么Java虚拟机会抛出一个内存溢出错误OutOfMemoryError。

六、帧(Frames

帧用于存储数据和部分结果,以及执行动态链接、方法的返回值和分派异常。

每次调用一个方法时,都会创建一个新的帧。
当方法调用完成时,帧将被销毁,无论该完成是正常的还是突然的(它抛出一个未捕获的异常)。
帧从创建框架的线程的Java虚拟机堆栈(2.5.2)中分配。
每一帧都有自己的局部变量数组(2.6.1)、自己的操作数堆栈(2.6.2),以及对当前方法类的运行时常量池(2.5.5)的引用。

一个框架可以使用附加的特定于实现的信息进行扩展,例如调试信息。

局部变量数组和操作数堆栈的大小在编译时确定,并与与帧相关联的方法的代码(4.7.3)一起提供。因此,帧数据结构的大小只取决于Java虚拟机的实现,并且这些结构的内存可以在方法调用时同时分配。

在给定的控制线程中的任何点上,只有一个帧,即执行方法的帧是活动的。
该帧称为当前帧,其方法称为当前方法。
在其中定义当前方法的类是当前类。对局部变量和操作数堆栈的操作通常参照当前帧。

如果一个帧的方法调用另一个方法或其方法已完成,则该帧不再是当前的。
当调用方法时,将创建一个新帧,并在控制传输到新方法时成为当前帧。
在方法返回时,当前帧会将其方法调用的结果重新传递给上一帧。
然后丢弃当前帧,因为前一帧变成当前帧。

请注意,由线程创建的框架对该线程是本地的,不能被任何其他线程引用。

6.1 本地变量(Local Variables)

每一帧都包含一个变量数组,称为其局部变量。
一个帧的局部变量数组的长度在编译时确定,并以一个类或接口的二进制表示形式与与该帧相关联的方法的代码一起提供(4.7.3)。

单个局部变量可以包含boolean, byte, char, short, int, float, reference, 或 returnAddress类型的值。一对局部变量可以包含一个long类型或double类型的值。

局部变量可以通过索引来解决。
第一个局部变量的索引为零。
当且仅当一个整数比局部变量数组的大小小0到1之间时,该整数被认为是局部变量数组的索引。

long类型或double类型的值占据两个连续的局部变量。这样的值只能使用较小的指数来处理。
例如,存储在索引n处的局部变量数组中的双类型值实际上占据了索引n和n+1的局部变量;
但是,索引n+1处的局部变量不能从加载。它可以被存储在以下文件中。
但是,这样做会使局部变量n的内容无效。

Java虚拟机不需要n是偶数。直观地说,long和double的值不需要在局部变量数组中64位对齐。
实现者可以自由地决定使用为该值保留的两个局部变量来表示这些值的适当方法。

Java虚拟机使用局部变量在方法调用时传递参数。
在类方法调用中,任何参数都在从局部变量0开始的连续局部变量中传递。
在实例方法调用时,本地变量0总是用于传递对正在调用实例方法的对象的引用(这在Java编程语言中)。任何参数随后都从局部变量1开始被传递到连续的局部变量中。

6.2 操作数堆栈(Operand Stacks)

每帧包含一个最后输出(LIFO)堆栈,称为其操作数堆栈。
一个帧的操作数堆栈的最大深度将在编译时确定,并与与该帧相关联的方法的代码一起提供(4.7.3)。

根据上下文,我们有时会将当前帧的操作数堆栈简单地称为操作数堆栈

当创建包含该操作数堆栈的帧时,该操作数堆栈为空。
Java虚拟机提供了要将本地变量或字段中的常量或值加载到操作数堆栈中的指令。
其他Java虚拟机指令从操作数堆栈中获取操作数,对它们进行操作,并将结果推回操作数堆栈中。
操作数堆栈还用于准备要传递给方法的参数,并接收方法结果。

例如,iadd指令(iadd)将两个int值添加在一起。
它要求要添加的int值是操作数堆栈的前两个值,由前面的指令推到那里。
这两个int值都将从操作数堆栈中弹出。它们被添加起来,它们的和被推回操作数堆栈中。
子计算可以嵌套在操作数堆栈上,从而产生所包含的计算可以使用的值。

操作数堆栈上的每个条目都可以保存任何Java虚拟机类型的值,包括一个long类型或double类型的值。 

操作数堆栈中的值必须以适合其类型的方式进行操作。
例如,不可能推两个int值,然后将它们视为长值,或者推两个浮点值,然后用iadd指令添加它们。
少量Java虚拟机指令(重复指令(dup)和交换(交换))在运行时数据区域作为原始值操作,而不考虑其特定类型;这些指令的定义方式不能用于修改或分解各个值。
这些对操作数堆栈操作的限制是通过类文件验证(4.10)来强制执行的。

在任何时间点,操作数堆栈都具有关联的深度,其中,long或double类型的值向深度贡献两个单位,而任何其他类型的值贡献一个单位。

6.3 动态链接(Dynamic Linking)

每个帧都包含一个对运行时常量池(2.5.5)的引用,用于当前方法的类型,以支持方法代码的动态链接。方法的类文件代码引用要调用的方法和通过符号引用访问的变量。
动态链接将这些符号方法引用转换为具体的方法引用,根据需要加载类以解析尚未定义的符号,并将变量访问转换为与这些变量的运行时位置相关联的存储结构中的适当偏移量。

方法和变量的这种后期绑定使得方法使用的其他类中的更改不太可能破坏这段代码。

6.4 正常方法调用完成(Normal Method Invocation Completion)

如果方法调用没有导致抛出异常(2.10),或者直接从Java虚拟机或由于执行显式抛出语句,则该方法调用将正常完成。
如果对当前方法的调用正常完成,则可以向调用方法返回一个值。
当调用的方法执行一个返回指令(2.11.8)时,就会发生这种情况,该指令的选择必须适合返回值的类型(如果有的话)。

在这种情况下,当前帧用于恢复调用程序的状态,包括它的本地变量和操作数堆栈,并且调用程序的程序计数器适当地增加,以跳过方法调用指令。然后在调用方法的帧中正常执行,将返回的值(如果有的话)推到该帧的操作数堆栈中。

6.5 突然的方法调用完成(Abrupt Method Invocation Completion)

如果在方法中执行的Java虚拟机指令导致Java虚拟机抛出异常(2.10),并且在该方法中没有处理该异常,则方法调用将突然完成。执行箭头指令(athrow)也会导致显式抛出异常,如果当前方法没有捕获该异常,则会导致方法突然的调用完成。突然完成的方法调用永远不会向其调用器返回值。

七、对象的表示(Representation of Objects)

Java虚拟机并不强制为对象指定任何特定的内部结构。

在一些Oracle的Java虚拟机的实现,引用一个类实例是一个指针处理本身就是一对指针:
一个表包含对象的方法和类对象的指针表示对象的类型,和另一个对象数据从堆分配的内存。

八、 浮点运算;流点数学(Floating-Point Arithmetic)

Java虚拟机包含了在IEEE标准中为二进制浮点算法(ANSI/IEEEStd。754-1985年,纽约)。

8.1 Java虚拟机浮点算法和IEEE 754

Java虚拟机支持的浮点算法与IEEE 754标准之间的关键区别在于:

  • Java虚拟机的浮点操作不会抛出异常、陷阱或指示IEEE 754无效操作、除零、溢出、下流或不精确的异常条件。Java虚拟机没有信令NaN值。
  • Java虚拟机不支持IEEE 754信令浮点比较。
  • Java虚拟机的舍入操作总是使用IEEE 754从舍入到最近的模式。
    不精确的结果四舍五入到最接近的可表示值,与最小显著位的值为零。
    这是IEEE 754的默认模式。但是Java虚拟机指令,将浮点类型的值转换为积分类型的值。
    Java虚拟机不提供任何更改浮点舍入模式的方法。
  • Java虚拟机不支持IEEE 754单扩展或双扩展格式,除了双扩展和双扩展指数值集可以说支持单扩展格式。可选择支持的浮点扩展指数值集和双扩展指数值集与IEEE 754扩展格式的值不对应:IEEE754扩展格式需要扩展精度和扩展指数范围。

8.2 浮点模式

每个方法都有一个浮点模式,要么是FP严格的,要么不是FP严格的。
方法的浮点模式可以通过设置定义方法的 method_info 结构(4.6)的 access_flags 项的ACC_STRICT标志来确定。设置此标志的方法是FP严格的;否则,该方法不是FP严格的。

注意,ACC_STRICT标志的映射意味着JDK1.1或更早版本中编译器编译的类中的方法实际上不是fp严格的。

当调用创建包含操作数栈的帧的方法具有浮点模式时,我们将把操作数栈称为具有给定的浮点模式。类似地,当包含Java虚拟机指令的方法具有该浮点模式时,我们将称Java虚拟机指令具有给定的浮点模式。

如果支持浮点扩展指数值集(2.3.2),则非FP严格的操作数堆栈上的float类型的值可以超出该值集,除非禁止值集转换(2.8.3)。如果支持double扩展指数值集(2.3.2),则在非fp严格的操作数堆栈上,double类型的值可以超过该值集,但值集转换禁止的情况除外。

在所有其他上下文中,无论是在操作数堆栈上还是在其他地方,无论浮点模式如何,float和double类型的float值只能分别超过float值集和double值集。特别是,类和实例字段、数组元素、局部变量和方法参数可能只包含从标准值集中提取的值。

8.3 值集转换(Value Set Conversion)

在特定的情况下,允许或需要使用一个支持扩展的浮点点值集的Java虚拟机的实现来映射一个扩展值集和标准值集之间的关联浮点类型的值。这种值集转换不是类型转换,而是与同一类型相关联的值集之间的映射。

如果指示了值集转换,则允许实现对值执行以下操作之一:

  • 如果该值的类型为浮点值,并且不是浮点值集的元素,则它会将该值映射到浮点值值集的最近的元素。
  • 如果该值的类型为双值,并且不是双值集的元素,则它将该值映射到双值集的最近的元素。

此外,如果指出了值集转换,则需要进行某些操作:

  • 假设执行非fp严格的Java虚拟机指令会导致float数类型的值被推到fp严格的操作数堆栈上,作为参数传递,或存储到本地变量、字段或数组的元素中。如果该值不是浮点值集的元素,则它将该值映射到浮点值集的最近的元素。
  • 假设执行非fp严格的Java虚拟机指令会导致double类型的值推到FPtrat的操作数堆栈上,作为参数传递或存储到本地变量、字段或数组的元素中。如果该值不是双值集的元素,则它将该值映射到双值集的最近的元素。

这种必需的值集转换可能是由于在方法调用期间传递了浮点类型的参数,包括本机方法调用;
返回非FP严格的方法的浮点类型的值;
或将浮点类型的值存储到非FP严格的方法中的局部变量、字段或数组中。

并不是一个扩展指数值集中的所有值都可以精确地映射到相应的标准值集中的值。
如果被映射的值太大而无法精确表示(它的指数大于标准值集所允许的指数),则将其转换为对应类型的(正或负)无穷大。
如果一个被映射的值太小而不能精确地表示(它的指数小于标准值集所允许的指数),它将四舍五入到最接近一个可表示的去正规化值或相同符号的零。

值集转换保留无穷大和NaNs,并且不能更改被转换值的符号。
值集转换对非浮点类型的值没有影响。

九、特殊方法

在Java虚拟机的级别上,每个用Java编程语言(JLS 8.8)编写的构造函数都作为一个实例初始化方法出现,该方法具有特殊的名称<init>。此名称由编译器提供。因为名称<init>不是一个有效的标识符,因此它不能直接在用Java编程语言编写的程序中使用。实例初始化方法只能在Java虚拟机中通过调用特殊指令(调用特殊指令)被调用,并且它们只能在未初始化的类实例上被调用。实例初始化方法具有派生出它的构造函数的访问权限(JLS 6.6)。

一个类或接口最多有一个类或接口初始化方法,并通过调用该方法进行初始化(5.5)。
类或接口的初始化方法具有特殊名称<clinit>,不接受参数,并且无效(4.3.3)。

类文件中名为<clinit>的其他方法没有任何后果。它们不是类或接口初始化方法。它们不能被任何Java虚拟机指令调用,也永远不会被Java虚拟机本身调用。

在版本号为51.0或以上的类文件中,该方法必须另外设置其ACC_STATIC标志(4.6),才能成为类或接口初始化方法。

这个要求是在JavaSE7中引入的。在一个版本号为50.0或以下的类文件中,一个名为<Clinit>的无效且不接受参数的方法被认为是类或接口初始化方法,无论其ACC_STATIC标志的设置如何。

名称<clinit>由编译器提供。因为名称<clinit>不是一个有效的标识符,所以它不能直接在用Java编程语言编写的程序中使用。类和接口初始化方法由Java虚拟机隐式调用;它们从不直接从任何Java虚拟机指令中调用,而只是作为类初始化过程的一部分间接调用。

如果以下所有项都为真,则方法为签名多态:

  • 它已在java.lang.invoke.MethodHandle类中定义。
  • 它有一个类型为Object[]的单一形式化参数。
  • 它有一个Object返回类型。
  • 它有 ACC_VARARGS 和  ACC_NATIVE标志集。

在JavaSE8中,唯一的签名多态方法是调用类java.lang.invoke的调用和调用类java.lang.invoke的精确方法。方法处理。

Java虚拟机对调用指令(调用指令invokevirtual)中的签名多态方法进行特殊处理,以实现对方法句柄的调用。方法句柄是对底层方法、构造函数、字段或类似的底层操作(5.4.3.5)的强类型的、直接可执行的引用,具有参数或返回值的可选转换。这些转换非常通用,并包括诸如转换、插入、删除和替换等模式。有关更多信息,请参见Java SE平台API中的java.lang.invoke软件包。

十、例外

Java虚拟机中的异常由可抛出类或其子类的实例表示。抛出异常会导致从抛出异常的点立即进行非本地控制转移。

大多数异常都是由于发生它们的线程执行的操作而同步发生的。相比之下,异步异常很可能发生在程序执行过程中的任何时候。Java虚拟机抛出一个异常,原因有三种:

  • 执行了athrow指令。
  • Java虚拟机同步检测到一个异常的执行条件。这些异常不会在程序中的任意点抛出,而是只在执行以下指令后同步抛出:
    • 指定可能导致的异常,例如:
      • 当指令包含违反Java编程语言语义的操作时,例如在数组的边界之外进行索引。
      • 当在加载或链接部分程序时出错时
    • 导致超过资源的一些限制,例如当使用过多的内存时。
  • 发生了一个异步异常,因为:
    • 已调用了类线程组或线程组的停止方法,或
    • 在Java虚拟机实现中发生了一个内部错误。一个线程可以调用stop方法来影响另一个线程或指定线程组中的所有线程。它们是异步的,因为它们可能发生在其他线程执行时的任何时候。一个内部错误被认为是异步的(6.3)。

Java在抛出异步异常之前,虚拟机可能允许发生少量但有限的执行。允许这个延迟,允许优化的代码在遵守Java编程语言的语义的同时检测和抛出这些异常。

一个简单的实现可能会在每个控制传输指令的点上轮询异步异常。由于程序的大小是有限的,因此这就提供了对检测异步异常的总延迟的限制。由于在控制传输之间不会发生异步异常,因此代码生成器有一定的灵活性来在控制传输之间重新排序计算,以获得更好的性能。马克·菲利对股票硬件进行了有效的民意调查。1993年函数式编程和计算机架构会议,哥本哈根,丹麦,页。179-187,建议作为进一步阅读。

Java虚拟机抛出的异常是精确的:当控制的转移发生时,在抛出异常的点之前执行的指令的所有效果都必须显得已经发生。在抛出异常的点之后发生的指令可能没有被计算。如果优化后的代码推测地执行了在异常发生点之后的一些指令,那么必须准备这样这些代码来隐藏程序的用户可见状态。

Java虚拟机中的每个方法都可能与零个或更多个异常处理程序相关联。异常处理程序指定实现异常处理程序活动的方法的Java虚拟机代码中的偏移量范围,描述异常处理程序能够处理的异常类型,并指定处理该异常的代码的位置。如果导致异常的指令的偏移量在异常处理程序的偏移量范围内,并且异常类型与异常处理程序处理的异常类的同一类或同一类的子类,则异常与异常处理程序匹配。当抛出异常时,Java虚拟机将在当前方法中搜索匹配的异常处理程序。如果找到匹配的异常处理程序,则系统将分支到匹配处理程序指定的异常处理代码。

如果在当前方法中没有找到此类异常处理程序,则当前方法调用将突然完成(2.6.5)。
在突然完成时,当前方法调用的操作数堆栈和局部变量将被丢弃,并弹出其帧,恢复调用方法的帧。
然后在调用者的框架的上下文中重新抛出异常,以此类推,继续沿着方法调用链向上。

如果在到达方法调用链的顶部之前没有找到合适的异常处理程序,则会终止在其中抛出异常的线程的执行。

为方法的异常处理程序搜索匹配项的顺序很重要。在一个类文件中,每个方法的异常处理程序都存储在一个表(4.7.3)中。在运行时,当抛出异常时,Java虚拟机会搜索当前方法的异常处理程序出现在类文件中相应的异常处理程序表中的顺序,从该表的开始开始。

请注意,Java虚拟机并不强制执行方法的异常表项的嵌套或任何顺序。Java编程语言的异常处理语义仅通过与编译器(3.12)的合作来实现。当通过其他方式生成类文件时,所定义的搜索过程将确保所有Java虚拟机实现的行为都将保持一致。

十一、指令集摘要(Instruction Set Summary)

 Java虚拟机指令由一个指定要执行的操作的单字节操作码组成,后面跟着零个或多个操作数,提供该操作所使用的参数或数据。许多指令都没有操作数,并且只包含一个操作码。

忽略异常,Java虚拟机解释器的内部循环是有效的。

do {
 atomically calculate pc and fetch opcode at pc;
 if (operands) fetch operands;
 execute the action for the opcode;
} while (there is more to do);

操作数的数量和大小由操作码决定。如果一个操作数的大小超过一个字节,则它首先以大端阶-高阶字节存储。例如,一个进入局部变量的无符号16位索引被存储为两个无符号字节,byte1和byte2,这样它的值是(byte1 << 8)|byte2。

字节码指令流只有单字节对齐的。两个例外情况是查找开关和表开关指令(查找开关、表开关),它们被填充以强制它们的一些操作数在4字节边界上的内部对齐。

将Java虚拟机操作码限制在一个字节内,并在编译代码中放弃数据对齐的决定反映了对紧凑性的有意识的偏见,可能以牺牲朴素实现中的某些性能为代价。一个单字节的操作码还限制了指令集的大小。不假设数据对齐意味着在许多机器上,大于一个字节的即时数据必须在运行时由字节构造。

11.1 类型和Java虚拟机

Java虚拟机指令集中的大多数指令都编码了关于它们所执行的操作的类型信息。
例如,iload指令(iload)将必须为int的局部变量的内容加载到操作数堆栈中。
浮动指令(浮动)也同样使用浮动值。这两个指令可能具有相同的实现,但有不同的操作码。

对于大多数类型的指令,指令类型在操作码助记符中用字母显式表示:
i表示int操作,l表示长,用s表示短,b表示字节,c表示char,f表示浮点,d表示双,a表示参考。
一些类型是明确的指令在其助记符中没有类型字母。
例如,数组长度总是操作于作为数组的对象。
一些指令,如goto,无条件控制转移,不会对类型操作数进行操作。

鉴于Java虚拟机的单字节操作码大小,将编码类型对其指令集的设计造成了压力。
如果每个输入的指令都支持Java虚拟机的所有运行时数据类型,那么将会有比可以用字节表示的更多的指令。相反,Java虚拟机的指令集为某些操作提供了一个简化级别的类型支持。
换句话说,指令集是有意地不正交的。根据需要,可以使用单独的指令在不受支持和受支持的数据类型之间进行转换。

表2.11.1-A总结了Java虚拟机指令集中的类型支持。通过将操作码列中的指令模板中的T替换为类型列中的字母,可以构建一个带有类型信息的特定指令。如果某个指令模板和类型的类型列为空,则不存在支持该类型操作的指令。例如,int类型和iload有一个加载指令,但是字节类型没有加载指令。

请注意,表2.11.1-A中的大多数指令都没有基本类型byte、char和short的形式。
没有针对boolean类型的表单。
编译器使用Java虚拟机对byte和short类型的文字值进行编码。
编译器使用Java虚拟机指令对byte和short类型的文字值的加载进行编码,该指令在编译时或运行时将这些值符号扩展为int类型的值。
布尔和字符类型的文本值加载使用指令进行编码,在编译时或运行时将文字扩展到int类型的值。
同样,从布尔值、字节值、字符值和字符值数组加载使用Java虚拟机指令进行编码,该指令将值符号扩展或零扩展到int类型的值。
因此,大多数对实际类型的布尔值、字节值、字符值和短值的操作都是通过操作计算类型int值的指令正确执行的。

Table 2.11.1-A. Java虚拟机指令集中的类型支持

 表2.11.1-B总结了Java虚拟机实际类型之间的映射与Java虚拟机计算类型之间的映射。

某些Java虚拟机指令,如pop和swap操作在操作数堆栈上而不考虑类型;
但是,这样的指令被限制只在某些计算类型的值上使用,也在表2.11.1-B中给出。

表2.11.1-B.Java虚拟机中的实际类型和计算类型

11.2 加载和存储说明

加载和存储指令在Java虚拟机框架(2.6)的本地变量(2.6.1)和操作数堆栈(2.6.2)之间传输值:

  • 将局部变量加载到操作数堆栈上:iload、iload_<>、lload、lload_<>、float、fload_<>、dfloat、dload_<>、aload、aload_<>。
  • 将操作数堆栈中的值存储到本地变量中:istore、istore_<>、lstore、lstore_<>、fstore、fstore_<>、dstore、dstore_<>、astore、astore_<>。
  • 将一个常数加载到操作数堆栈上:bipush、sipush、ldc、ldc_w,ldc2_w、aconst_null、iconst_m1,iconst_<i>,lconst_<l>,fconst_<f>,dconst_<d>.
  • 使用更广泛的索引获得访问更多的局部变量,或访问更大的直接操作数:wide。

访问对象和数组(2.11.5)的元素字段的指令也会向操作数堆栈传输数据。

上面所列的指令助记符和圆括号之间的后尾字母(例如,iload_<>)表示指令族(iload_<>为成员iload_0、iload_1、iload_2和iload_3)。这类指令族是一个需要一个操作数的附加通用指令(iload)的专门化。对于专门的指令,操作数是隐式的,不需要存储或获取。语义是相同的(iload_0与操作数0的iload的意思相同)。尖括号之间的字母指定了该指令族的隐式操作数的类型:对于<n>,一个非负整数;
对于<i>,一个int;
对于<l>,一个long;
对于<f>,一个float;
对于<d>,一个double。
在许多情况下,类型中用于对short、byte和short(2.11.1)的值执行操作。

这个指令族的符号在整个规范中都在使用。

11.3 算术指令(Arithmetic Instructions)

算术指令计算的结果通常是操作数堆栈上的两个值的函数,并将结果推回操作数堆栈上。
主要有两种算术指令:一种是操作整数值,另一种是操作浮点值
在这些类型中,算术指令都是专门用于Java虚拟机的数字类型的。
不直接支持对byte、short类型和char类型(2.11.1)的整数运算或boolean类型的值进行整数运算;
这些操作由操作类型int的指令处理。Integer指令和float指令在溢出和除以零问题上的行为也有所不同。算术说明如下:

  • Add: iadd, ladd, fadd, dadd.
  • Subtract: isub, lsub, fsub, dsub.
  • Multiply: imul, lmul, fmul, dmul.
  • Divide: idiv, ldiv, fdiv, ddiv.
  • Remainder: irem, lrem, frem, drem.
  • Negate: ineg, lneg, fneg, dneg.
  • Shift: ishl, ishr, iushr, lshl, lshr, lushr.
  • Bitwise OR: ior, lor.
  • Bitwise AND: iand, land.
  • Bitwise exclusive OR: ixor, lxor.
  • Local variable increment: iinc.
  • Comparison: dcmpg, dcmpl, fcmpg, fcmpl, lcmp.

Java在整数值和浮点值(JLS 4.2.2,JLS 4.2.4)上的Java编程语言操作符的语义直接由Java虚拟机指令集的语义支持。

Java虚拟机在对整数数据类型的操作期间不显示溢出。唯一可以抛出异常的整数运算是整数除法指令(idiv和ldiv)和整数余数指令(irem和lrem),如果除数为零,它们就会抛出一个ArithmeticException运算异常。

浮点数上的Java虚拟机操作按照IEEE754中指定的行为执行。特别是,Java虚拟机需要完全支持IEEE 754正规化浮点数和逐渐下流,这使得更容易证明特定数值算法的理想特性。

Java虚拟机要求浮点运算术的行为就好像每个浮点运算符都将其浮点结果四舍五入到结果精度一样。不精确的结果必须四舍五入到最接近无限精确结果的可表示值;如果两个最接近的可表示值距离相等,则选择具有最小显著位为零的那个值。这是IEEE 754标准的默认舍入模式,称为圆到最近的模式。

当将浮点值转换为整数时,Java虚拟机使用IEEE 754向零模式。这将导致数字被截断;表示操作数值的分数部分的任何有效位都将被丢弃。向零模式选择类型的值最接近,但大小不大于无限精确的结果。

Java虚拟机的浮点运算符不会抛出运行时异常(不要与IEEE 754浮点异常相混淆)。溢出的操作产生一个有符号无穷大,溢出的操作产生一个去标准值或一个有符号零,一个没有数学上确定结果的操作产生NaN。所有以NaN作为操作数的数值操作都会产生NaN。

对long类型(lcmp)值的比较执行有符号比较。浮点类型(dcmpg、dcmpl、fcmpg、fcmpl)的值使用IEEE 754非信号比较进行比较。

11.4 类型转换说明(Type Conversion Instructions)

类型转换指令允许在Java虚拟机数字类型之间进行转换。这些可以用于在用户代码中实现显式转换,或者减轻Java虚拟机指令集中正交性的缺失。

Java虚拟机直接支持以下扩展的数字转换:

  • int 转换为 long, float, or double
  • long 转换为 float or double
  • float 转换为 double

加宽的数字转换指令为i2l、i2f、i2d、l2f、l2d和f2d。这些操作码的助记符很简单,因为类型指令有命名约定,2的双关语表示“to”。例如,i2d指令将一个int值转换为一个double。

大多数扩展的数值转换不会丢失关于一个数值的总体大小的信息。实际上,从int到长和int到double的转换根本不会丢失任何信息;数值完全保留。从浮点扩展到fp严格(2.8.2)的转换也完全保留了数值;只有没有FPtrat的转换可能会丢失关于转换值的总体大小的信息。

从int到浮点的转换,或从长到浮点,或从长到双的转换,可能会失去精度,即可能失去一些最小的值;得到的浮点值是整数值的正确四舍入版本,使用IEEE 754舍入到最近的模式。

尽管可能会丢失精度,但扩大数值转换并不会导致Java虚拟机抛出运行时异常(不要与IEEE 754浮点异常混淆)。

将int的数字转换扩展为long的简单符号-扩展了int值的两个补码表示,以填充更宽的格式。
将char的扩展数值转换扩展为整型零-扩展了字符值的表示,以填充更宽的格式。

请注意,不存在从整数类型byte、char和short到类型int的扩展数字转换。
如2.11.1中所述,byte、char和short的值在内部被拓宽为int类型,这使得这些转换隐式。

Java虚拟机还直接支持以下缩小范围的数字转换:

  • int 转换为 byte, short, or char
  • long 转换为int
  • float 转换为int or long
  • double 转换为 int, long, or float

缩小的数字转换指令为i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
缩小的数值转换可能导致不同的符号、不同的数量级,或两者都有;它可能会失去精度。

从int或长到积分类型T的缩小数值转换简单地丢弃除n个最低阶位之外的所有比特,其中n是用于表示类型T的比特数。这可能导致结果值与输入值不具有相同的符号。

在从浮点值到积分类型T的缩小数值转换中,其中T为int或long,浮点值转换如下:

  • 如果浮点值为NaN,则转换的结果为int或长0。
  • 否则,如果浮点值不是无穷大,则使用IEEE 754向零模式将浮点值舍入为整数值V。
    有两种情况:
    • 如果T是long的,并且这个整数值可以表示为一个long值,那么结果就是long值V。
    • 如果T的类型是int,并且这个整数值可以表示为int,那么结果就是int值V。
  • 否则
    • 该值必须太小(大幅度的负值或负无穷大),并且结果是int或long类型的最小可表示值。
    • 或者该值必须太大(大幅度的正值或正无穷大),并且结果是int或long类型的最大可表示值。

从双倍到浮动的缩小数值转换行为符合IEEE754。使用IEEE 754正确地舍入到最近的模式。
太小而不能表示为浮点数的值将转换为浮点数类型的正零或负零;
太大而不能表示为浮点数的值为转换为正无穷大或负无穷大。
双NaN总是被转换为浮动NaN。

尽管可能会发生溢出、下流或精度丢失,但缩小数字类型之间的转换范围永远不会导致Java虚拟机抛出运行时异常(不要与IEEE 754浮点异常混淆)。

11.5 对象创建和操作(Object Creation and Manipulation)

虽然类实例和数组都是对象,但Java虚拟机会使用不同的指令集来创建和操作类实例和数组:

  • 创建一个新的类实例:new
  • 创建一个新的阵列:newarray, anewarray, multianewarray
  • 访问类的字段(static字段,称为类变量)和类实例的字段(非static字段,称为实例变量):
    getstatic, putstatic, getfield, putfield。
  • 将数组组件加载到操作数堆栈上:baload, caload, saload, iaload, laload, faload, daload, aaload.
  • 将操作数堆栈中的值存储为数组组件:bastore, castore, sastore, iastore, lastore, fastore, dastore, aastore。
  • 获取数组的长度:arraylength
  • 检查类实例或数组的属性:instanceof, checkcast

11.6 操作员堆栈管理说明(Operand Stack Management Instructions)

提供了许多关于直接操作操作数堆栈的指令:pop, pop2, dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2, swap

11.7 控制转移指令(Control Transfer Instructions)

控制传输指令有条件地或无条件地使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。

Java虚拟机有不同的指令集,这些指令有条件地在与int和引用类型的数据进行比较时进行分支。
它还具有不同的条件分支指令,用来测试null引用,因此不需要为null(2.4)指定一个具体的值。

使用int比较指令(2.11.1)执行类型布尔、字节、字符和短数据之间比较的条件分支。
比较类型长、浮点或双的数据之间的条件分支,使用指令启动,比较数据并产生比较的int结果(2.11.3)。后续的int比较指令将测试这个结果并影响条件分支。
由于它强调int比较,Java虚拟机为int类型提供了条件分支指令的丰富补充。

所有的int条件控制传输指令都会执行有符号的比较。

11.8 方法调用和返回指令(Method Invocation and Return Instructions)

以下五条指令调用方法:

  • invokevirtual 调用对象的实例方法,并在对象的(虚拟)类型上进行调度。
    这是Java编程语言中的普通方法分派。
  • invokeinterface 调用一个接口方法,搜索由特定运行时对象实现的方法,以找到适当的方法。
  • invokespecial 调用需要特殊处理的实例方法,无论是实例初始化方法(2.9)、private方法,还是超类方法。
  • invokestatic 在命名类中调用类(静态)方法。
  • invokedynamic 调用绑定到调用动态指令的调用站点对象的目标。
    调用站点对象被绑定到调用Java虚拟机调用动态指令的特定词汇出现,这是在第一次执行指令之前运行引导方法的结果。
    因此,与其他调用方法的指令不同,每次调用动态指令的出现都有一个唯一的链接状态。

按返回类型区分的方法返回指令,包括返回(用于返回类型布尔、字节、字符、短或int)、返回、返回、返回和返回的值。此外,返回指令用于从声明为void的方法、实例初始化方法以及类或接口初始化方法。

11.9 抛出异常

使用路径指令以编程方式抛出异常。如果各种Java虚拟机指令检测到异常情况,它们也会抛出异常。

11.10 同步(Synchronization)

Java虚拟机支持通过单个同步构造:监视器来同步方法中的方法和指令序列。

方法级同步是隐式执行的,作为方法调用和返回的一部分(2.11.8)。同步方法在运行时常量池的metod_info结构(4.6)中由ACC_SYNCHRONIZED标志区分,该标志由方法调用指令检查。当调用设置了ACC_SYNCHRONIZED的方法时,执行线程进入监视器,调用方法本身,并在方法调用是否正常或突然完成时退出监视器。在执行线程拥有监视器期间,没有其他线程可以进入监视器。如果在调用同步方法时抛出异常,并且同步方法不处理该异常,则在将异常重新抛出出同步方法之前,该方法的监视器将自动退出。

指令序列的同步通常用于对Java编程语言的同步块进行编码。Java虚拟机提供监视器和监视器退出指令,以支持这类语言构造。同步块的正确实现需要来自针对Java虚拟机的编译器的协作(编译器(3.14)。

结构化锁定是指在方法调用期间,给定监视器上的每个出口都与该监视器上的前一个条目相匹配的情况。由于不能保证提交给Java虚拟机的所有代码都将执行结构化锁定,所以Java虚拟机的实现是允许的,但不需要强制执行以下两个保证结构化锁定的规则。让T是一个线程,M是一个监视器。然后

  1. T在方法调用过程中对M执行的监视器条目数必须等于T在方法调用过程中对M执行的监视器退出数,无论方法调用是正常完成还是突然完成。
  2. 在方法调用期间,T在M上执行的监视器退出的数量,因为方法调用不会超过自方法调用以来T在M上执行的监视器条目的数量。

请注意,当调用同步方法时,Java虚拟机在同步方法时自动执行的监视器输入和退出被认为是在调用方法的调用期间发生的。

十二、 类库(Class Libraries)

Java虚拟机必须为Java SE平台的类库的实现提供足够的支持。
如果没有Java虚拟机的合作,这些库中的一些类就无法实现。

可能需要Java虚拟机特殊支持的类包括支持的类:

  • 反射,例如 java.lang.reflect 包中的类和类Class。
  • 加载和一个类或接口的创建,最明显的例子是类的ClassLoader类加载器。
  • 链接和初始化一个类或接口。上面引用的示例类也属于这一类。
  • 安全性,例如 java包中的类。安全性和其他类,如SecurityManager安全管理器。
  • 多线程,比如Thread类。
  • 弱引用,例如在 java.lang.ref 包中的类

上面的清单是为了说明说明性的,而不是全面的。这些类或它们所提供的功能的详尽列表超出了本规范的范围。有关详细信息,请参阅Java SE平台类库的规范。

十三、公共设计,私有实现(Public Design, Private Implementation)

到目前为止,该规范已经概述了Java虚拟机的公共视图:class文件格式和指令集。
这些组件对于Java虚拟机的硬件独立性、操作系统独立性和实现独立性至关重要。
实现者可能更愿意将它们看作是一种在每个实现Java SE平台的主机之间安全地通信程序片段的手段,而不是作为一个需要精确遵循的蓝图。

理解公共设计和私有实现之间的界限是很重要的。
一个Java虚拟机实现必须能够读取类文件,并且必须精确地实现其中的Java虚拟机代码的语义。
其中一种方法是将此文档作为一个规范,并从字面上实现该规范。
但是,实现者在本规范的约束条件下修改或优化实现也是完全可行和可取的。
只要可以读取 class 文件格式并维护其代码的语义,实现者就可以以任何方式实现这些语义。
“底层”是实现者的业务,只要仔细维护正确的外部接口。

也有一些例外:调试器、分析器和即时代码生成器都需要访问Java虚拟机的元素,这些元素通常被认为是“底层”。在适当的情况下,Oracle与其他Java虚拟机实现者和工具供应商合作,开发到Java虚拟机的通用接口,以便这些工具使用,并在整个行业中推广这些接口。

实现者可以使用这种灵活性为Java、低内存使用或可移植性定制高性能虚拟机实现。
在给定的实现中什么有意义取决于该实现的目标。
实现选项的范围包括:

  • 在加载时或执行期间将Java虚拟机代码转换为另一个虚拟机的指令集。
  • 在加载时或执行期间将Java虚拟机代码转换为主机CPU的本机指令集(有时称为准时制,或JIT,代码生成)。

精确定义的虚拟机和对象文件格式的存在并不需要显著地限制实现者的创造力。
Java虚拟机旨在支持许多不同的实现,提供新的和有趣的解决方案,同时保持实现之间的兼容性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值