翻译Java虚拟机的结构
这个文件指定了一个抽象机器。 它没有描述Java虚拟机的任何特定的实现。
要正确实现Java虚拟机,只需要能够读取class
文件格式并正确执行其中指定的操作即可。 不属于Java虚拟机规范的实现细节将不必要地限制实现者的创造力。 例如,运行时数据区域的内存布局,所使用的垃圾收集算法以及Java虚拟机指令的任何内部优化(例如将它们转换为机器代码)均由实现者自行决定。
在本规范中对Unicode的所有引用都是针对可在http://www.unicode.org/
获得的Unicode标准6.0.0版给出的。
由Java虚拟机执行的编译代码使用与硬件和操作系统无关的二进制格式表示,典型地(但不一定)存储在被称为class
文件格式的文件中。 class
文件格式精确地定义了一个类或接口的表示形式,其中包括诸如字节排序等细节,这些信息可能被认为是特定于平台的对象文件格式。
与Java编程语言一样,Java虚拟机操作两种类型: 基本类型和引用类型 。 相应地,有两种类型的值可以存储在变量中,作为参数传递,通过方法返回,并在原始值和参考值上进行操作 。
Java虚拟机希望几乎所有的类型检查都是在运行时间之前完成的,通常由编译器来完成,而不必由Java虚拟机本身完成。 原始类型的值不需要被标记或以其他方式被检查以在运行时确定它们的类型,或者与参考类型的值区分。 相反,Java虚拟机的指令集使用指令操作特定类型的值来区分其操作数类型。 例如, iadd , ladd , fadd和dadd都是Java虚拟机指令,它们会添加两个数值并生成数值结果,但每个指令都分别专用于其操作数类型: int
, long
, float
和double
。 有关Java虚拟机指令集中类型支持的概述,请参阅§2.11.1 。
Java虚拟机包含对对象的明确支持。 一个对象是一个动态分配的类实例或一个数组。 对对象的引用被认为具有Java虚拟机类型reference
。 类型reference
值可以被看作是指向对象的指针。可能存在对一个对象的多个引用。 对象总是在类型reference
值上运行,传递和测试。
Java虚拟机支持的基本数据类型是数字类型 , boolean
类型( §2.3.4 )和returnAddress
类型( §2.3.3 )。
数字类型由整型 ( §2.3.1 )和浮点型 ( §2.3.2 )组成。
boolean
类型的值将真值true
和false
进行编码,默认值为false
。
Java®虚拟机规范的第一版没有将boolean
视为Java虚拟机类型。 但是, boolean
值在Java虚拟机中的支持有限。 TheJava®Virtual Machine Specification的第二版通过将boolean
类型视为一种类型来阐明了这个问题。
returnAddress
类型的值是指向Java虚拟机指令操作码的指针。 在原始类型中,只有returnAddress
类型不直接与Java编程语言类型相关联。
浮点类型是float
和double
,它们在概念上与32位单精度和64位双精度格式的IEEE 754值和在IEEE标准中针对二进制浮点运算 (ANSI / IEEE标准754-1985,纽约)。
IEEE 754标准不仅包括正负符号幅度值,还包括正负的零,正负的无穷大和一个特殊的非数值(以下简称“NaN”)。 NaN值用于表示某些无效操作的结果,例如零除零。
Java虚拟机的每个实现都需要支持两组浮点值,称为浮点值集和双值集 。 另外,Java虚拟机的实现可以选择支持两个扩展指数浮点值集中的一个或两个,称为浮点扩展指数值集和双扩展指数值集。 在某些情况下,可以使用这些扩展指数值集而不是标准值集来表示float
或double
类型的值。
任何浮点值集合的有限非零值都可以表示为s · m · 2 ( eN + 1) ,其中s是+1或-1, m是小于2N的正整数, e是E min = - (2 K -1 -2)和E max = 2 K -1 -1(含)之间的整数,并且其中N和K是取决于所设置的值的参数。 有些价值观可以用不止一种方式来表示, 例如,假设一个值集合中的一个值v可以用s , m和e的某些值以这种形式表示,那么如果m发生偶然和e小于2 K -1 , m并将e增加1以产生相同值v的第二表示。 如果m≥2N -1 ,则将这种形式的表示称为归一化 ; 否则表示被称为非规范化 。 如果一个值集合中的值不能以m≥2N -1的方式表示,那么这个值就被认为是一个非规格化的值 ,因为它没有规范化的表示。
表2.3.2-A总结了两个必需的和两个可选浮点值集合的参数N和K (以及导出参数E min和E max )的约束条件。
表2.3.2-A。 浮点值设置参数
参数 | 浮动 | 浮扩展-指数 | 双 | 双扩展指数 |
---|---|---|---|---|
ñ | 24 | 24 | 53 | 53 |
ķ | 8 | ≥11 | 11 | ≥15 |
E 最大 | +127 | ≥+ 1023 | +1023 | ≥ + 16383 |
E 分钟 | -126 | ≤- 1022 | -1022 | ≤- 16382 |
在一个实现支持一个或两个扩展指数值集合的情况下,对于每个支持的扩展指数值集合,都有一个具体的实现相关常量K ,其值受表2.3.2-A的限制; 这个值K反过来决定E min和E max的值 。
四个数值集中的每一个都不仅包含上面描述的有限非零值,还包括正数零,负数零,正无穷大,负数无穷大和NaN五个值。
请注意, 表2.3.2-A中的约束被设计为使得浮点值集合的每个元素都必然也是浮点扩展指数值集合的一个元素,双倍值集合和双倍扩展指数值组。 同样,双值集合的每个元素也必然是双重扩展指数值集合的元素。 每个扩展指数值集具有比相应的标准值集更大的指数值范围,但没有更高的精度。
除了只有一个NaN值(IEEE 754指定2 24 -2个不同的NaN值)之外,浮点值集的元素正好是可以使用IEEE 754标准中定义的单个浮点格式表示的值。 除了只有一个NaN值(IEEE 754指定了2 53-2个不同的NaN值)之外,double值集合的元素正好是可以使用IEEE 754标准中定义的双重浮点格式表示的值。 但是请注意,这里定义的浮点扩展指数和双扩展指数值集的元素并不分别对应于可以使用IEEE 754单扩展和双扩展格式表示的值。 除非浮点值必须以class
文件格式( §4.4.4 , §4.4.5 )表示,否则本规范不要求对浮点值集的值进行特定表示。
float,float-extended-exponent,double和double-extended-exponent值集不是类型。 Java虚拟机的实现使用浮点值集的一个元素来表示float
类型的值总是正确的; 但是,在某些情况下,实现可能允许使用float-extended-exponent值集的一个元素。 类似地,对于一个实现来说,使用double值集的一个元素来表示double
类型的值总是正确的; 然而,在某些情况下,实现可能允许使用双扩展指数值集的元素。
除NaN之外,浮点值集的值是有序的 。 当从最小到最大排列时,它们是负无穷,负有限值,正负零,正有限值和正无穷大。
浮点正零和浮点负零比较相等,但还有其他的操作可以区分它们; 例如,将1.0
除以0.0
产生正无穷大,但将1.0
除以-0.0
会产生负无穷大。
NaN是无序的 ,所以数值比较的数值比较和测试的值都是false
如果它们中的任何一个或两个都是NaN。 特别是,当且仅当该值为NaN时,对值的数值相等的测试才具有值false
。 对于数字不等式的测试,如果任一操作数是NaN,则其值为true
。
returnAddress
类型由Java虚拟机的jsr , ret和jsr_w指令使用( §jsr , §ret , §jsr_w )。 returnAddress
类型的值是指向Java虚拟机指令操作码的指针。 与数字基元类型不同, returnAddress
类型不对应于任何Java编程语言类型,并且不能由正在运行的程序修改。
尽管Java虚拟机定义了一个boolean
类型,但它只能提供非常有限的支持。 没有专用于boolean
值操作的Java虚拟机指令。 相反,Java编程语言中操作boolean
值的表达式被编译为使用Java虚拟机的int
数据类型的值。
Java虚拟机直接支持boolean
数组。 它的newarray指令( §newarray )可以创建boolean
数组。 使用byte
数组指令baload和bastore ( §baload , §bastore )访问和修改boolean
类型的数组。
在Oracle的Java虚拟机实现中,Java编程语言中的boolean
数组被编码为Java虚拟机byte
数组,每个boolean
元素使用8位。
Java虚拟机使用1
来表示true
和0
来表示false
来对boolean
数组组件进行编码。 在Java编程语言boolean
值被编译器映射到Java虚拟机类型int
值的地方,编译器必须使用相同的编码。
有三种reference
类型:类类型,数组类型和接口类型。 它们的值分别是对动态创建的类实例,数组,或实现接口的类实例或数组的引用。
数组类型由具有单个维度的组件类型 (其长度不由该类型给出)组成。 数组类型的组件类型本身可以是数组类型。 如果从任何数组类型开始,考虑它的组件类型,然后(如果这也是一个数组类型)该类型的组件类型等等,最终必须达到不是数组类型的组件类型; 这被称为数组类型的元素类型。 数组类型的元素类型必须是原始类型,类类型或接口类型。
一个reference
值也可以是特殊的空引用,对没有对象的引用,这里用null
表示。 null
引用最初没有运行时类型,但可以转换为任何类型。 reference
类型的默认值为null
。
Java虚拟机定义了程序执行期间使用的各种运行时数据区域。 其中一些数据区域是在Java虚拟机启动时创建的,并且只有在Java虚拟机退出时才会被销毁。 其他数据区域是每个线程。 每个线程数据区域在线程退出时创建和销毁时创建。
Java虚拟机可以同时支持多个执行线程(JLS§17)。 每个Java虚拟机线程都有自己的pc
(程序计数器)寄存器。 在任何时候,每个Java虚拟机线程正在执行单个方法的代码,即该线程的当前方法( §2.6 )。 如果该方法不是native
,则pc
寄存器包含当前正在执行的Java虚拟机指令的地址。 如果线程当前正在执行的方法是native
,那么Java虚拟机的pc
寄存器的值是未定义的。 Java虚拟机的pc
寄存器足够宽以在特定平台上保存returnAddress
或本机指针。
每个Java虚拟机线程都有一个私有Java虚拟机堆栈 ,与线程同时创建。 Java虚拟机堆栈存储帧(第2.6节 )。 Java虚拟机堆栈类似于传统语言(如C语言)的堆栈:它包含局部变量和部分结果,并在方法调用和返回中起作用。 由于除了推送和弹出帧之外,Java虚拟机堆栈不会被直接操作,因此可能会分配堆栈。 Java虚拟机堆栈的内存不需要是连续的。
在Java®虚拟机规范的第一版中,Java虚拟机堆栈被称为Java堆栈 。
这个规范允许Java虚拟机栈或者是固定的大小,或者是按照计算的需要动态扩展和收缩。 如果Java虚拟机堆栈的大小是固定的,则每个Java虚拟机堆栈的大小可以在创建堆栈时独立选择。
Java虚拟机实现可以提供程序员或用户控制Java虚拟机堆栈的初始大小,以及在动态扩展或收缩Java虚拟机堆栈的情况下控制最大和最小大小。
Java虚拟机具有在所有Java虚拟机线程中共享的堆 。 堆是所有类实例和数组的内存分配的运行时数据区。
堆是在虚拟机启动时创建的。 对象的堆存储由自动存储管理系统(称为垃圾收集器 ) 回收 ; 对象从不显式释放。 Java虚拟机假定没有特定类型的自动存储管理系统,并且存储管理技术可以根据实现者的系统要求来选择。 堆可以是固定的大小,或者可以根据计算的需要而扩展,并且如果不需要更大的堆,那么堆可以被缩小。 堆的内存不需要是连续的。
Java虚拟机实现可以提供程序员或用户控制堆的初始大小,以及如果堆可以动态扩展或收缩,则控制最大和最小堆大小。
Java虚拟机具有在所有Java虚拟机线程之间共享的方法区域 。 方法区域类似于常规语言的编译代码的存储区域,或类似于操作系统进程中的“文本”段。 它存储每个类的结构,如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化和接口初始化的特殊方法( §2.9 )。
方法区域是在虚拟机启动时创建的。 尽管方法区域在逻辑上是堆的一部分,但是简单的实现可以选择不垃圾收集或压缩它。 本规范不要求方法区域的位置或用于管理编译代码的策略。 方法区域可以具有固定的大小,或者可以根据计算的需要进行扩展,并且如果更大的方法区域变得不必要,则可以缩小方法区域。 方法区域的内存不需要是连续的。
Java虚拟机实现可以提供程序员或用户对方法区域的初始大小的控制,以及在变大小方法区域的情况下控制最大和最小方法区域大小。
运行时常量池是class
文件( §4.4 )中constant_pool
表的每类或每界面运行时表示。 它包含几种常量,从编译时已知的数字文字到必须在运行时解析的方法和字段引用。 运行时常量池提供的功能类似于传统编程语言符号表的功能,虽然它包含比典型符号表更广泛的数据范围。
每个运行时常量池都是从Java虚拟机的方法区域( §2.5.4 )分配的。 类或接口的运行时常量池是在Java虚拟机创建类或接口( §5.3 )时构建的。
有关构建运行时常量池的信息,请参阅第5节( 加载,链接和初始化 ) 。
Java虚拟机的实现可以使用俗称的“C堆栈”来支持native
方法(用Java编程语言以外的语言编写的方法)的传统堆栈。 原生方法堆栈也可以通过为诸如C语言的Java虚拟机的指令集执行解释器来使用。不能加载native
方法并且本身不依赖于传统堆栈的Java虚拟机实现不需要提供本地方法栈。 如果提供,则在创建每个线程时,通常为每个线程分配本地方法堆栈。
该规范允许本地方法栈或者具有固定的大小,或者根据计算的需要动态扩展和收缩。 如果本地方法堆栈的大小是固定的,那么当创建堆栈时,每个本地方法堆栈的大小可以独立选择。
Java虚拟机实现可以提供程序员或用户对本地方法堆栈的初始大小的控制,以及在大小变化的本地方法堆栈的情况下控制最大和最小方法堆栈大小。
一个框架用于存储数据和部分结果,以及执行动态链接,返回方法的值和调度异常。
每次调用方法时都会创建一个新的框架。 当方法调用完成时,框架被销毁,无论这个完成是正常的还是突然的(它引发一个未捕获的异常)。 帧是从创建帧的线程的Java虚拟机堆栈(第2.5.2节)分配的。 每个帧都有自己的局部变量数组( §2.6.1 ),自己的操作数栈( §2.6.2 ),以及对当前方法的类的运行时常量池( §2.5.5 )的引用。
一个框架可以用额外的特定于实现的信息来扩展,比如调试信息。
局部变量数组和操作数栈的大小在编译时确定,并与框架相关的方法( §4.7.3 )一起提供。 因此,框架数据结构的大小仅取决于Java虚拟机的实现,并且可以在方法调用时同时分配这些结构的内存。
只有一个框架,执行方法的框架,在给定的控制线程中的任何点处都是活动的。 该帧被称为当前帧 ,其方法被称为当前方法 。 当前方法定义的类是当前类 。 本地变量和操作数栈的操作通常是参考当前帧。
如果一个框架的方法调用另一个方法或者其方法完成,框架将不再是最新的。 当一个方法被调用时,一个新的框架被创建,并在控制转移到新的方法时变成当前的。 在方法返回时,当前帧将其方法调用的结果(如果有)返回到前一帧。 当前帧随着前一帧变成当前帧而被丢弃。
请注意,由线程创建的框架对于该线程是本地的,并且不能被任何其他线程引用。
每个帧( §2.6 )都包含一个称为局部变量的变量数组 。 帧的局部变量数组的长度在编译时确定,并以类或接口的二进制表示形式提供,以及与帧相关联的方法的代码(第4.7.3节 )。
一个局部变量可以包含boolean
, byte
, char
, short
, int
, float
, reference
或returnAddress
类型的boolean
。 一对局部变量可以保存long
或double
类型的值。
局部变量通过索引来解决。 第一个局部变量的索引是零。 一个整数被认为是局部变量数组的一个索引,当且仅当该整数在0到1之间,小于局部变量数组的大小。
long
类型或double
类型的值占用两个连续的局部变量。 这样的价值只能用较小的指数来解决。 例如,在索引为n的局部变量数组中存储的double
类型的值实际上占据了索引为n和n + 1的局部变量; 但是,在索引n +1的本地变量不能从中加载。 它可以存储进去。 但是,这样做会使局部变量n的内容无效。
Java虚拟机不要求n是偶数。 直观地说,类型long
和double
值在局部变量数组中不需要64位对齐。 实现者可以自由决定使用为该值保留的两个局部变量来表示这些值的适当方式。
Java虚拟机使用局部变量在方法调用上传递参数。 在类方法调用中,任何参数都是从本地变量0开始的连续局部变量中传递的。 在实例方法调用中,总是使用局部变量0将引用传递给调用实例方法的对象(在Java编程语言中)。 随后从局部变量1开始,连续的局部变量传递任何参数。
每个帧( §2.6 )都包含一个名为操作数堆栈的后进先出(LIFO) 堆栈 。 一个帧的操作数堆栈的最大深度是在编译时确定的,并与框架相关的方法的代码一起提供( §4.7.3 )。
在上下文中清楚的地方,我们有时会将当前帧的操作数堆栈简称为操作数堆栈。
当包含它的帧被创建时,操作数栈是空的。 Java虚拟机提供指令将常量或值从本地变量或字段加载到操作数堆栈中。 其他Java虚拟机指令从操作数堆栈中取操作数,对它们进行操作,并将结果返回到操作数堆栈中。 操作数堆栈也用于准备参数传递给方法和接收方法结果。
例如, iadd指令( §iadd )将两个int
值一起添加。 它要求将被添加的int
值作为操作数堆栈的前两个值,由前面的指令推送。 这两个int
值都从操作数堆栈中弹出。 他们被添加,他们的总和被推回到操作数堆栈。 子计算可以嵌套在操作数堆栈上,从而得到可由包含计算使用的值。
操作数堆栈中的每个条目都可以包含任何Java虚拟机类型的值,包括long
类型或double
类型的值。
操作数堆栈中的值必须以适合其类型的方式进行操作。 例如,推动两个int
值,然后把它们作为一个long
int
或推入两个float
值,然后用iadd指令添加它们是不可能的 。 少量的Java虚拟机指令( dup指令( dup )和swap ( swap ))在运行时数据区域以原始值运行,而不考虑它们的具体类型; 这些指令的定义是不能用来修改或分解个别值的。在操作数堆栈操作这些限制是通过强制执行class
文件验证(§4.10)。
在任何时间点,操作数堆栈具有相关联的深度,其中类型的值long
或double
有助于两个单元的深度和任何其他类型的值贡献一个单元。
每个帧(§2.6)包含对运行时间常量池(基准2.5.5节当前方法的类型来支持)的动态链接的方法的代码。的class
一种方法文件代码指的是被调用的方法和通过符号引用将被访问的变量。动态链接转换这些符号方法引用到具体的方法的引用,根据需要加载类来解决的尚未未定义的符号,并将其转换可变访问到与这些变量的运行时间的位置相关联的存储结构适当地偏移。
在一些Java虚拟机的Oracle的实施方式中的,一类实例的引用是一个指针指向一个手柄,其本身一对指针:一个用于包含对象的方法和一个指向一个表Class
,它表示该对象型的对象,而另一个从堆中的对象数据分配的内存。
Java虚拟机包含在指定的浮点运算的一个子集IEEE二进制浮点运算(ANSI / IEEE标准754-1985,纽约)。
由Java虚拟机和IEEE 754标准支持的浮点运算之间的主要区别是:
-
Java虚拟机的浮点运算不会抛出异常,陷阱,或以其他方式零,溢出,下溢,或不准确的信号无效操作,除法的IEEE 754得天独厚的条件。Java虚拟机没有信号NaN的价值。
-
Java虚拟机的取整运算总是使用IEEE 754向最接近模式。不精确结果四舍五入到最接近的可表示值,与领带要以零最低显著位值。这是IEEE 754默认模式。但是,浮点类型的值转换为整型值的Java虚拟机指令圆趋向于零。Java虚拟机不给任何方式改变浮点舍入模式。
-
Java虚拟机不支持任何的IEEE 754单扩展,双扩展格式,但只要可以说双和双扩展,指数值设置为支持单扩展格式。浮子-扩展指数和扩展双 - 指数值集,其可任选被支持,不对应于的IEEE 754扩展格式的值:该IEEE 754扩展格式需要延长的精度以及延长指数范围。
每一种方法都有一个浮点模式,其是FP-严格或不FP-严格。的方法的浮点模式由设置决定ACC_STRICT
所述的标志access_flags
的项的method_info
结构(§4.6)定义方法。指此标志被设置的方法是FP-严格; 否则,该方法是不FP-严格。
请注意,这个映射ACC_STRICT
标志意味着,在JDK编译器编译的类中的方法释放1.1或更早版本都不能有效FP-严格。
我们将把一个操作数堆栈作为具有给定的浮点模式时,其调用的方法创建包含操作数堆栈具有浮点模式的帧。同样,我们将把Java虚拟机指令为具有给定的浮点模式,当包含指令,该方法具有浮点模式。
如果浮子扩展的指数值组被支撑(§2.3.2),类型的值float
不是FP-严格的操作数堆栈上的范围可以超过该值设置除非由值集转换禁止(§2.8.3) 。如果双延长的指数值组被支撑(§2.3.2),类型的值double
的操作数堆栈不是FP-严格上的范围可以超过该值设置除非由值集转换禁止。
在所有其他情况下,无论操作数堆栈上或其它地方,也不管浮点模式,键入的浮点值float
以及double
可以仅在范围内浮动值设置和双值集合,分别。特别地,类和实例字段,数组元素,局部变量和方法参数可以仅包含从标准值集合得出的值。
支持扩展的浮点值集合中的Java虚拟机的实现被允许或要求,在特定情况下,以映射的扩展和标准值集合之间的相关联的浮点类型的值。这样的值来设置转换不是一个类型的转换,但具有相同的类型相关联的值集之间的映射。
其中值集的转换被指示时,一种实现被允许执行对某个值以下操作之一:
Such required value set conversions may occur as a result of passing a parameter of a floating-point type during method invocation, including native
method invocation; returning a value of a floating-point type from a method that is not FP-strict to a method that is FP-strict; or storing a value of a floating-point type into a local variable, a field, or an array in a method that is not FP-strict.
不从一个扩展的指数值集合中的所有值可以准确地映射到对应的标准值集合的值。如果正被映射到的值过大,无法精确表示(其指数比由标准值集合允许更大的),它被转换为相应的类型的(正或负)无穷大。如果正被映射到的值太小,精确表示(其指数比由标准值集合允许更小),则舍入到最近可表示的非规格化值或相同的符号的零的。
在Java虚拟机的水平,用Java编程语言(JLS§8.8)每一个构造显示为一个实例的初始化方法具有特殊名称<init>
。这个名字是由编译器提供。由于名称<init>
是不是有效的标识符,它不能直接用Java编程语言编写的程序中使用。实例的初始化方法可能只在Java虚拟机被调用invokespecial指令(第invokespecial),并且它们可以只对未初始化的类实例调用。实例初始化方法取从衍生它的构造方法的访问权限(JLS 6.6节)。
类或接口具有至多一个类或接口初始化方法和被初始化(§5.5通过调用该方法)。类或接口的初始化方法具有特殊的名字<clinit>
,不带任何参数,并且是无效的(§4.3.3)。
命名其他方法<clinit>
在class
文件中都没有结果。他们不是类或接口初始化方法。它们不能被任何Java虚拟机指令被调用,并且永远不会被Java虚拟机本身的调用。
在一个class
其版本号为51.0或以上的文件中,该方法另外还必须有其ACC_STATIC
标志(§4.6以便为类或接口初始化方法设定)。
这个要求是在Java SE 7中引入的一个类文件,其版本号为50.0或以下,一个名为方法<clinit>
是无效和不带任何参数被认为是类或接口初始化方法,无论其设置的ACC_STATIC
标志。
这个名字<clinit>
是由编译器提供。由于名称<clinit>
是不是有效的标识符,它不能直接用Java编程语言编写的程序中使用。类和接口的初始化方法是由Java虚拟机里隐含调用; 他们是决不会从任何Java虚拟机指令调用,但只是间接调用的类的初始化过程的一部分。
在Java SE 8,唯一的签名多态性方法是invoke
与invokeExact
类的方法java.lang.invoke.MethodHandle
。
Java虚拟机提供了特殊处理,以在签名多态性方法invokevirtual指令(第invokevirtual),以实现一个的调用方法处理。一种方法,手柄是强类型,直接可执行的参考到底层方法,构造,场,或类似的低层次操作(§5.4.3.5)中,用的参数可选变换或返回值。这些变换是相当普遍的,并且包括诸如图案作为转换,插入,缺失和取代。见java.lang.invoke
在Java SE平台API包以获取更多信息。
在Java虚拟机的异常是由一个类的实例表示Throwable
或它的一个子类。在从那里异常被抛出点控制的直接外地转移抛出一个异常的结果。
大多数异常同步发生,通过在它们发生的线程的行为的结果。异步异常,与此相反,可以潜在地发生在一个程序的执行的任何点。Java虚拟机抛出的三个原因之一例外:
Java虚拟机可以允许执行的小,但数量有限,抛出异步异常之前发生。这种延迟允许允许进行优化的代码来检测和分它是可行的处理它们,同时遵守Java编程语言的语义抛出这些异常。
一个简单的实现可能轮询在每个控制转移指令的点异步异常。由于一个程序具有有限的尺寸,这提供了关于总延迟一个绑定中检测异步异常。由于没有异步异常将控制转移之间发生,代码发生器具有一定的灵活性以重新排序控制传输之间的计算获得更好的性能。本文有效地轮询对股票硬件马克·菲利,PROC。在函数式编程和计算机体系结构1993年会议,哥本哈根,丹麦,第179-187,建议进一步阅读。
由Java虚拟机抛出的异常被精确:当控制权转移发生,指令所有特效从中抛出异常必须出现有发生点之前执行。从中抛出异常点后出现没有说明可能出现了评价。如果优化的代码已推测执行的一些随后在其中发生异常的点处的指令,这样的代码必须准备隐藏从该程序的用户可见的状态这个推测执行。
在Java虚拟机的每个方法可与零个或多个相关联的异常处理程序。异常处理程序指定的偏移到实现用于该异常处理程序是活性的方法中的Java虚拟机代码的范围内,描述异常类型,该异常处理程序能够处理,并指定为处理代码的位置该异常。一个异常的异常处理程序相匹配导致异常是在异常处理程序的偏移量的范围和异常类型是相同的类或类异常的异常处理程序处理的一个子类的指令如果偏移。当一个异常被抛出,Java虚拟机搜索当前方法匹配的异常处理程序。如果匹配的异常处理程序被发现,该系统转移到由匹配的处理程序中规定的异常处理代码。
如果在当前方法没有找到这样的异常处理程序,当前方法调用突然结束(§2.6.5)。上突然结束后,将操作数堆栈和当前方法调用的本地变量被丢弃,且其帧被弹出,复原调用方法的框架。该异常然后在调用者的帧的上下文重新抛出等,持续了方法调用链。如果达到了方法调用链的顶部之前没有发现适合的异常处理程序,其中,所述异常发生的线程的执行结束。
其中一个方法的异常处理程序中查找匹配的顺序很重要。在一个class
文件中,每个方法的异常处理程序被存储在表(§4.7.3)。在运行时,当一个异常被抛出,Java虚拟机将搜索当前方法的异常处理程序的顺序,它们出现在相应的异常处理程序表中class
的文件,从该表的开头开始。
需要注意的是Java虚拟机不会强制嵌套或方法的异常表项的任何顺序。Java编程语言的异常处理语义仅通过与编译器(合作实施§3.12)。当class
文件被通过其他方式产生的,所定义的搜索程序,确保所有的Java虚拟机实现的行为一致。
Java虚拟机指令由一字节的操作码指定要执行的操作,后面的零点或更多的操作数供给由操作使用的参数或数据。许多指令没有操作数,仅由一个操作码的。
做{ 原子计算PC,并获取在PC操作码; 如果(操作数)取操作数; 执行用于操作码的操作; }而(有更多的事情要做);
的操作数的数目和大小由操作码来确定。如果操作数的大小超过一个字节,则存储在大端顺序-高位字节。例如,一个16位无符号索引到局部变量被存储为两个无符号字节,字节1和字节2,使得其值是(字节1 <<
8)| 字节2。
字节码指令流是唯一的单字节对齐。两个例外是lookupswitch和tableswitch指令(§ lookupswitch,§ tableswitch),其被填充以迫使上4字节边界的一些操作数的内部对准。
Java虚拟机操作码限制为一个字节,并放弃编译的代码中的数据排列的决定反映了有利于紧凑的有意识的偏见,可能在幼稚实现的一些性能为代价的。一个一字节操作码也限制了指令集的大小。不假设数据对齐意味着大于一个字节立即数据必须从字节在运行时被构造在许多机器上。
大部分的Java虚拟机指令,指令集对他们执行的操作编码类型的信息。例如,ILOAD指令(§ ILOAD)加载的局部变量,它必须是一个的内容int
,到操作数栈。该FLOAD指令(第FLOAD)确实有相同的float
价值。的两条指令可以具有相同的实现方式中,但具有不同的操作码。
对于大多数类型的指令,该指令类型在由字母操作码助记符显式地表示:我用于int
操作,升为long
,s ^为short
,b为byte
,Ç为char
,˚F为float
,d为double
,和一个为reference
。有些指令有类型是明确没有在他们的记忆类型的信。例如,arraylength始终工作的对象,则在阵列上。一些指令,如跳转 ,无条件控制转移,不要在输入的操作数。
由于Java虚拟机的一个字节的操作码大小,编码类型为操作码放在其指令集的设计压力。如果每个输入指令支持的所有Java虚拟机的运行时间数据类型的,将有超过一个字节可以表示更多的指令。相反,指令集Java虚拟机提供的某些操作类型的支持水平降低。换言之,该指令集是故意不正交。单独指令可以被用来在必要时不支持和支持的数据类型之间进行转换。
表2.11.1-A总结了指令集Java虚拟机的类型支持。的特定指令,与类型信息,通过替换内置Ť通过在类型列字母在指令模板在操作码列中。如果由于某种指令模板和类型类型栏为空白,则没有指令存在支持该类型的操作。例如,有一个类型的负载指令int
,ILOAD,但对于类型没有加载指令byte
。
请注意,在大多数指令表2.11.1-A不具备整型形式byte
,char
和short
。无有形式boolean
类型。编译器编码类型的文字值的负载byte
和short
使用Java虚拟机指令符号扩展的值类型的值int
在编译时或运行时。的类型的文字值的载荷boolean
和char
正在使用的字面零扩展到类型的值的指令编码int
在编译时或运行时。同样地,从类型的值的阵列的负载boolean
,byte
,short
,和char
正在使用的Java虚拟机指令,符号扩展或零值,延伸到类型的值编码的int
。因此,实际的类型的值的大多数操作boolean
,byte
,char
,和short
正确地通过在计算类型的值操作说明进行int
。
表2.11.1-A。类型支持Java虚拟机指令集
操作码 | byte | short | int | long | float | double | char | reference |
---|---|---|---|---|---|---|---|---|
Tipush | bipush | sipush | ||||||
TCONST | ICONST | lconst | FCONST | DCONST | aconst | |||
t加载 | ILOAD | 的Iload | FLOAD | DLOAD | 负载 | |||
Tstore | istore | lstore | FSTORE | dstore | 一家商店 | |||
TINC | iinc | |||||||
Taload | baload | saload | iaload | laload | faload | daload | caload | aaload |
Tastore | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore |
塔德 | 我加 | 拉德 | FADD | DADD | ||||
TSUB | ISUB | LSUB | 一个fsub | DSUB | ||||
Tmul | IMUL | lmul | FMUL | DMUL | ||||
Tdiv | IDIV | LDIV | FDIV | DDIV | ||||
兰珍珍 | IREM | lrem | FREM | 德雷姆 | ||||
TNEG | INEG | lneg | FNEG | dneg | ||||
Tshl | ISHL | lshl | ||||||
TSHR | ISHR | LSHR | ||||||
Tushr | iushr | lushr | ||||||
T和 | 我和 | 土地 | ||||||
托尔 | IOR | LOR | ||||||
Txor | ixor | lxor | ||||||
I2T | I2B | I2S | I2L | I2F | I2D | |||
L2T | l2i | L2F | L2D | |||||
F2T | F2I | F2L | F2D | |||||
D2T | D2I | D2L | D2F | |||||
TCMP | LCMP | |||||||
Tcmpl | fcmpl | dcmpl | ||||||
Tcmpg | fcmpg | dcmpg | ||||||
if_TcmpOP | if_icmpOP | if_acmpOP | ||||||
Treturn | 我回来 | lreturn | freturn | dreturn | 回报 |
Java虚拟机的实际类型和Java虚拟机的计算类型之间的映射是通过汇总表2.11.1-B 。
某些Java虚拟机指令,如流行和交换,而不考虑输入操作数堆栈上运行; 然而,这样的指令被限制为仅在某些类别的计算类型,在给出的值使用表2.11.1-B 。
加载和存储指令的局部变量(之间传送值§2.6.1)和操作数堆栈(§2.6.2 Java虚拟机帧(的)§2.6):
-
加载本地变量压入操作数堆栈:ILOAD,iload_ <N> ,的Iload,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> 。
访问对象的字段和阵列元件中的指令(§2.11.5)也传送数据到和来自操作数堆栈。
与后角撑架之间的信上面所示的指令助记符(例如,iload_ <N> )表示的指令家族(其成员iload_0,iload_1,iload_2,和iload_3在的情况下iload_ <N> )。的指令,例如家庭是一个额外的通用指令(的特ILOAD,它有一个操作数)。对于特定的指令,操作数是隐含的,并不需要存储或取出。语义是否则相同的(iload_0意味着同样的事情ILOAD与操作数0)。尖括号之间的信指定隐含操作数的类型为家庭的指令:对于<N> ,一个非负整数; 为<I> ,一个int
; 为<L> ,一个long
; 为<F> ,一个float
; 和用于<d> ,一个double
。对于类型的形式int
在很多情况下,用于在类型的值执行操作byte
,char
和short
(§2.11.1)。
算术指令计算结果,其通常操作数堆栈上的两个值的函数,推结果返回操作数堆栈上。主要有两种类型的算术指令:这些整数值运行,而那些对浮点值运行。在每一个这类的运算指令是专门为Java虚拟机的数字类型。存在用于整数算术上的值不直接支持byte
,short
和char
类型(§2.11.1),或用于值boolean
类型;这些操作是由上式操作说明进行处理int
。整数和浮点指令也在他们的行为有所不同溢出和除以零。算术指令如下:
Java编程语言运营商的整数和浮点值(JLS§4.2.2,JLS§4.2.4)的语义直接由Java虚拟机指令集的语义支持。
Java虚拟机并不表示整数数据类型的操作过程中溢出。可以抛出异常的唯一整数操作是整数除法指令(IDIV和LDIV)和整数余数的指令(IREM和lrem),其抛出ArithmeticException
如果除数为零。
在浮点数的Java虚拟机操作的行为在IEEE 754特别指定的,Java虚拟机需要全面支持IEEE 754的非规范化的浮点数和渐进下溢,这使得它更容易证明的特定数值算法所需的特性。
Java虚拟机需要浮点运算表现得好像每一个浮点操作的四舍五入浮点结果到结果的精度。不精确的结果必须被舍入到表示值最接近于无限精确的结果;如果两个最接近的可表示值同等的附近,选择具有零至少显著位之一。这是IEEE 754标准的默认舍入模式,被称为向最接近模式。
Java虚拟机使用IEEE 754 向零舍入浮点值转换为整数时的模式。这导致在被截断的数目;表示操作数值的小数部分的有效数字的任何位被丢弃。向零模式回合选择作为其结果最接近类型的值,但幅度比,无限精确的结果没有更大。
Java虚拟机的浮点运算符不抛出运行时异常(不符合IEEE 754浮点异常混淆)。该溢出的操作产生一个签名的无穷大,即下溢的操作产生非正规化值或符号零,并且没有明确的数学结果产生的NaN出操作。与南作为一个操作数的所有数字运算产生NaN的结果。
上类型的值的比较long
(LCMP)执行签名比较。上浮点类型(的值进行比较dcmpg,dcmpl,fcmpg,fcmpl使用IEEE 754非信令比较被执行)。
类型转换指令允许Java虚拟机的数字类型之间的转换。这些可以被用来实现在用户代码显式转换或减轻在指令集Java虚拟机的缺乏正交性。
日益扩大的数字转换指令I2L,I2F,I2D,L2F,L2D和F2D。这些操作码的助记符是直接给出了类型化的说明和双关语使用的2是指的命名约定“到了。”例如,I2D指令转换的int
一个值double
。
大多数扩大数字转换不会失去对数值的总体规模的信息。事实上,从转换扩大int
到long
并int
以double
不任何信息丢失;数值正好保存。换算从加宽float
到double
属于FP-严格(§2.8.2)也保存的数值完全相同;只有这样的转换不属于FP-严格可能会失去约转换后的值的总大小的信息。
从转化int
到float
,或从long
到float
,或从long
到double
,可能会失去精度,即,可能会丢失一些值的至少显著比特; 所得浮点值是一个正确的舍入版本的整数值,使用IEEE 754舍入到最近的模式。
尽管可能出现的精度损失,扩大数字转换不会导致Java虚拟机抛出一个运行时异常(不带IEEE 754浮点异常混淆)。
的A扩大数值转换int
到long
简单地登录延伸的二进制补码表示int
的值来填充较宽格式。的加宽数值转换char
为整型零延伸的表示char
值来填充较宽格式。
需要注意的是扩大数字转换不会从整型存在byte
,char
和short
打字int
。正如指出的§2.11.1类型,值byte
,char
以及short
在内部加宽输入int
,使得这些转换隐式的。
在缩小数字转换指令I2B,I2C,I2S,l2i,F2I,F2L,D2I,D2L和D2F。甲变窄数值转换可导致不同的符号,以不同的顺序大小,或两者的值; 它可能因此而丧失精度。
的A缩小数字转换int
或long
为整型Ť简单地丢弃所有,但Ñ最低阶位,其中Ñ是用于表示类型比特的数目Ť。这可能导致所得到的值不具有相同的符号作为输入值。
在一个浮点值的为整型变窄数字转换Ť,其中Ť要么int
或long
,浮点值被转换如下:
从A变窄数值转换double
到float
根据IEEE 754。结果使用IEEE 754舍入到最近模式被正确地舍入的行为。过小的值被表示为float
被转换成正或负型的零float
; 作为要表示太大的值,float
被转换为正或负无穷大。甲double
NaN被总是被转换为一float
为NaN。
尽管可能出现溢出,下溢或精度损失的事实,数字类型之间的缩小转换不会导致Java虚拟机抛出一个运行时异常(不带IEEE 754浮点异常混淆)。
该控制转移指令有条件或无条件导致Java虚拟机继续与一个比控制传输指令之后其他的指令执行。 他们是:
Java虚拟机具有明显的集上与数据比较有条件分支指令int
和reference
类型。它也有不同的条件分支指令,其测试对于空引用,因此它不需要指定一个具体的值null
(§2.4)。
上的类型的数据之间的比较的条件分支boolean
,byte
,char
,和short
使用执行int
比较指令(§2.11.1)。上的类型的数据之间的比较的条件分支long
,float
或者double
使用用于比较的数据,并产生一个指令被启动int
的比较(结果§2.11.3)。随后int
比较指令测试这个结果和效果的条件分支。由于公司注重的int
比较,Java虚拟机提供了类型的条件分支指令的丰富的补充int
。
-
invokespecial调用要求特殊处理的实例方法,无论是一个实例初始化方法( §2.9),一个
private
方法或超类方法。 -
invokedynamic调用其是键合于该调用位置对象的目标的方法 invokedynamic指令。呼叫站点对象被绑定到的特定词汇出现 invokedynamic由Java虚拟机的指令的第一执行之前运行的引导方法的结果的指令。因此,在每次出现时 invokedynamic指令具有唯一的链接状态,不同于调用方法的其它指令。
该方法返回指令,其通过返回类型区分,是ireturn(用于返回类型的值boolean
,byte
,char
,short
,或int
),lreturn,freturn,dreturn,和areturn。此外,返回指令被用来从被宣布为无效方法,实例初始化方法和类或接口的初始化方法返回。
Java虚拟机支持的两种方法和方法中的指令序列由单个同步结构同步:该显示器。
方法级同步是隐含执行,如方法调用和返回(的一部分§2.11.8)。甲synchronized
方法是在运行时间常量池中的区分method_info
结构(§4.6)由ACC_SYNCHRONIZED
标志,这是由方法调用指令进行检查。当调用用于其的方法ACC_SYNCHRONIZED
被设置,正在执行的线程进入监视器,调用方法本身,并离开显示器的方法调用是否正常或突然结束。在执行线程拥有该显示器的时候,没有其他线程可以进入。如果是一个例外的调用过程中引发的synchronized
方法和synchronized
方法不处理异常,该方法的监控异常被重新抛出了之前会自动退出synchronized
方法。
指令序列的同步典型地用于将编码synchronized
的Java编程语言的块。Java虚拟机提供的monitorenter和monitorexit指令来支持这样的语言结构。正确执行synchronized
块需要从编译器针对Java虚拟机(合作§3.14)。
结构的锁定是这种情况时,在方法调用期间,给定的显示器上每一个退出该显示器上一前一入口相匹配。由于没有保证提交给Java虚拟机的所有代码将执行结构性锁定,Java虚拟机的实现是允许的,但不要求强制执行两个以下两条规则保证结构的锁定。让牛逼是一个线程,中号是监视器。然后:
Java虚拟机必须提供Java SE平台的类库实现足够的支持。一些在这些库的类不能没有Java虚拟机的合作来实现。
上面的列表是意在说明,而不是全面的。这些类的或提供的功能详尽的清单超出了本规范的范围。请参阅Java SE平台类库的规格细节。
迄今为止,该规范已拟定了Java虚拟机的公众观点:class
文件格式和指令集。这些组件的硬件,运行系统-和Java虚拟机实现的独立性至关重要。实现者可能更愿意认为他们是安全通信的各执行的Java SE平台,而不是作为一个蓝图准确地跟踪主机之间的节目片段的手段。
理解,公众的设计和私有实现之间的界限就在于它是非常重要的。 Java虚拟机实现必须能够读取class
文件,必须严格执行其中的Java虚拟机代码的语义。这样做的一种方式是利用这个文件作为规范和逐字实现该规范。但是,这也是完全可行的,并期望实现者来修改或优化本说明书的约束内执行。只要class
文件格式可以读取和其代码的语义保持,实现者可以实现任何方式对这些语义。什么是“引擎盖下”是实现者的业务,只要正确的外部接口,精心维护。
也有一些例外:调试器,分析器,以及刚刚在时间码生成器可以在每个需要访问那些通常认为是Java虚拟机的元素在适当情况下,甲骨文的作品与其他Java虚拟机“引擎盖下”。实现者与工具厂商开发的通用接口的Java虚拟机使用此类工具,并推动整个行业的接口。
实现者可以利用这种灵活性来定制的Java虚拟机实现了高性能,低内存占用,或便携性。情理之中的事情给定的实现依赖于实现的目标。实施方案的范围包括以下内容:
精确定义的虚拟机和目标文件格式的存在,不必显著限制实现者的创造力。Java虚拟机被设计为支持许多不同的实现,提供新的和有趣的解决方案,同时保留实现之间的兼容性。