Java虚拟机结构——Java虚拟机规范

前言#

在文章中,经常能看到说着说着中文突然出来一段英文,这可能是因为这段的原理译者也不太了解,所以不敢乱加翻译,或者是这一段根本没必要翻译。

如果遇到这种格式的:

长段的英文句子

这应该是上面的一句话翻译了,但是又怕和原文差之千里,所以贴上原文。

下面开始是原文了

===

原文:Chapter 2. The Structure of the Java Virtual Machine

这篇文档定义了一个抽象的Java虚拟机,并不是某一个特定的Java虚拟机实现。

要想正确的实现Java虚拟机,你只需要能正确的读取class文件格式,执行定义在其中的操作即可。关于其实现细节,这并不是Java虚拟机规范中的一部分,因为没必要限制JVM实现者的创造力。例如,运行时数据区的内存布局;Java垃圾收集器使用的算法;任何JVM指令集的内部优化(比如它们转译成机器代码)的实现细节都没有具体的限制,实现者具有自行决定如何实现的自由。

All references to Unicode in this specification are given with respect to The Unicode Standard, Version 13.0, available at https://www.unicode.org/.

class文件格式#

可以被Java虚拟机执行的编译后代码表现为一种独立于硬件和操作系统的二进制格式,通常(但并不必要)存储在一个文件中,称为class文件格式。class文件格式精确的定义了类或接口的表示,including details such as byte ordering that might be taken for granted in a platform-specific object file format.

第四章、“类文件格式”包含了class文件的细节。

数据类型#

和Java编程语言一样,JVM提供了两种类型:基本类型(primitive types)和引用类型(reference types)。相应的,有两种类型的值可以被保存在变量中、作为参数传递、被方法返回和操作,它们就是:基本值和引用值。

JVM期望在运行时,几乎所有的类型检查都已经完成,通常是通过一个编译器或者是JVM自己(但不是必要的)来完成这种检查。
基本类型的值并不需要在运行时被标记或是以其它方式被检查,以确定它们的类型,或者是与引用类型的值所区分。相反,JVM的指令集使用与特定类型的值进行操作的指令来区分它们的操作数类型。比如:iaddladdfaddddadd是JVM中将两个数字相加并且产生一个数字结果的的所有指令,相应地,每一个指令都指定了它们的操作数类型:intlongfloatdouble。 For a summary of type support in the Java Virtual Machine instruction set, see §2.11.1.

JVM包含对对象的显示支持。一个对象既可能是一个被动态分配的类的实例也可能是一个数组。 A reference to an object is considered to have Java Virtual Machine type reference.reference类型的值可以被想象成一个对象的指针。一个对象可能存在多个引用,对象总是通过一个reference类型的值被操作,传递和测试。

译者:上面所说的reference类型的值,是JVM层面中的一种值,Java中并没有这个说法。并且这里说的基本类型、引用类型值都是JVM层面中的,并非Java层面中的,读者需要以JVM的角度进行思考。

原始类型和值#

JVM支持的基本类型有数字类型boolean类型和returnAddress类型。

数字类型包括整数类型浮点类型

整数类型包括:

  • byte,它的值为8位的有符号的二进制补码数(two's-complement),它的默认值是0
  • short,它的值为16位的有符号的二进制补码数(two's-complement),它的默认值是0
  • int,它的值为32位的有符号的二进制补码数(two's-complement),它的默认值是0
  • long,它的值为64位的有符号的二进制补码数(two's-complement),它的默认值是0
  • char,它的值为16位的无符号整数,表示Unicode code points in the Basic Multilingual Plane,被编码为UTF-16,并且它们的默认值是null code point('\u0000')

浮点类型包括:

  • float,它的值精准的遵循了32位IEEE754 binary32格式的值表示,并且它的默认值是正0
  • double,它的值精准的遵循了64位IEEE754 binary64格式的值表示,并且它的默认值是正0

boolean类型的值将真假值编码为truefalse,并且默认值是false

第一版的JVM规范没有考虑将boolean作为一个JVM类型。However, boolean values do have limited support in the Java Virtual Machine. The Second Edition of The Java® Virtual Machine Specification clarified the issue by treating boolean as a type.

returnAddress类型的值是一个指向JVM指令集操作码的指针。在所有基本类型中,只有returnAddress类型并不直接与Java编程语言中的类型相关。

整数类型和值#

浮点类型和值#

returnAddress类型和值#

returnAddress类型被JVM中的jsrretjsr_w指令所使用,returnAddress类型的值是一个指向JVM指令集操作码的指针。与数字基本类型不同,returnAddress并不与任何Java编程语言中的类型相关联,并且不能被运行中的程序修改。

boolean类型#

尽管JVM定义了一个boolean类型,但也仅仅只为它提供了一些非常有限的支持。JVM指令集中并没有用来单独对boolean值执行操作的指令,相反,Java编程语言中对boolean值进行操作的表达式被编译为使用JVM中int数据类型的值。

JVM并不直接支持boolean数组,newarray指令允许创建一个boolean数组的。但一个boolean类型的数组将被以byte数组的指令(baloadbastore)被访问和修改。

在Oracle的JVM实现中,Java编程语言中的boolean数组将被编码为JVM的byte数组,每一个boolean元素使用8位。

JVM将boolean数组中的组件使用1代表true和0代表false来编码。Java编程语言中的boolean值将被编译器映射成JVM中int类型的值,对此,编译器必须使用相同的编码。

引用类型和值#

有三种引用类型:类类型,数组类型和接口类型。相应的,它们的值分别是一个动态创建的类实例、数组或一个实现了接口的类实例或数组的引用。

一个数组类型具有一个单一维度的组件类型(它的长度并没有由类型给出)。一个数组类型的组件类型本身也可能是一个数组类型。
如果从一个任意的数组类型开始,考虑它的组件类型,然后(如果组件类型也是一个数组类型)考虑组件类型的组件类型,等等等等,最终必定会到达一个不是数组类型的组件类型,这个类型称作数组类型的元素类型。一个数组的元素类型必须是基本类型、类类型或一个接口类型。

一个引用值也可能是特定的null引用,对于一个没有引用到任何对象的引用,就通过null表示。
null引用初始情况下没有运行时类型,但是可能转换成任意的类型,引用类型的默认值是null

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

This specification does not mandate a concrete value encoding null.

运行时数据区#

JVM定义了多个在程序执行期间被使用的运行时数据区。其中的一些数据区跟随JVM的生命周期,在JVM启动时被创建,仅在JVM退出时被销毁。其它的数据区跟随线程的生命周期,线程数据区(Per-thread data areas)在一个线程被创建时创建并且在一个线程退出时被销毁。

pc寄存器#

JVM可以支持多个线程在同一时刻被执行。每一个JVM线程拥有它自己的pc(程序计数器)寄存器。在任一时刻,每一个JVM线程执行代码中的一个单一方法,该方法被称作线程的当前方法。如果一个方法不是native的(不是本地方法),pc计数器就包含了当前正在执行的JVM指令的地址。如果当前线程执行的方法是native的,pc寄存器的值是未定义的。JVM的pc寄存器定义的足够宽,以容纳一个的returnAddress或一个特定平台上的本地指针。

Java虚拟机栈(Java Virtual Machine Stack)#

每一个Java虚拟机线程有它私有的Java虚拟机栈,与线程在同一时刻被创建。一个Java虚拟机栈中存储着栈帧。一个Java虚拟机栈类似于传统编程语言(如C)中的栈,它保存着本地变量和部分结果并且在方法调用和方法返回中扮演重要的部分(plays a part)。

it holds local variables and partial results, and plays a part in method invocation and return.

因为除了push和pop栈帧之外,Java虚拟机栈从不会被直接操作,所以帧可以在堆中被分配。Java虚拟机栈在内存中并不必须是连续的。

在第一版Java虚拟机规范中,Java虚拟机栈被称作Java栈

这份定义允许Java虚拟机栈具有固定大小或根据计算出的需求动态扩展和收缩。如果Java虚拟机栈具有固定大小,那么每一个Java虚拟机栈的大小都可以在栈被创建时独立的被选择。

一个Java虚拟机实现可能提供一个由程序员或用户控制的初始Java虚拟机栈大小,同样,也可能在一个动态扩展和收缩的Java虚拟机栈中控制最大和最小栈大小

下面的异常条件与Java虚拟机栈相关

  1. 如果在一个线程中的计算需要一个比允许的Java虚拟机栈大小更大的栈,JVM抛出StackOverflowError
  2. 如果JVM栈可以被动态扩展,并且已经尝试扩展但是并没有足够的内存可以完成这个扩展,或者没有足够的内存可以创建一个线程的初始Java虚拟机栈,JVM抛出OutOfMemoryError

堆(Heap)#

Java虚拟机有一个被所有JVM线程共享的(Heap)。堆是为所有类实例和数组分配内存的运行时数据区。

堆在JVM启动时被创建。堆中存储的对象被自动存储管理系统(俗称垃圾回收器)回收,对象永远不会被显式的释放。JVM没有假设(它具有)特定的类型的自动存储管理系统,可以根据实现者的系统需求来选择合适的存储管理技术。堆可能是固定大小的或者根据计算出的需要进行动态扩展收缩的。堆的内存并不需要是连续的。

一个JVM实现可以提供一个程序员或用户控制的初始堆大小,与之类似,如果堆可以被动态扩展收缩,可以控制最大和最小堆大小

如下异常条件和堆相关:

  • 如果计算得知需要比自动内存管理系统可以获得的更多的堆空间,JVM抛出OutOfMemoryError

方法区(Method Area)#

JVM具有一个在所有JVM线程间共享的方法区。方法就像一个传统编程语言的编译后代码的存储区域,或者像是操作系统进程中的文本段。它保存每个类(pre-class)的结构,比如运行时常量池、域和方法数据,并且包括方法和构造器的代码,包括类或接口初始化和实例初始化时所使用的特殊方法。

方法区在JVM启动时被创建,尽管方法区是堆的一个逻辑部分,但该区域的简单实现可能选择让该区不被垃圾收集或压缩。这份定义不强制要求方法区的位置或者管理编译后代码所使用的策略。方法区可以是固定大小或可以根据计算所得的需求被动态扩展和收缩的。方法区的内存并不需要是连续的。

一个JVM实现可以提供一个程序员或用户控制的初始方法区大小,与之类似,如果方法区可以被动态扩展收缩,可以控制最大和最小方法区大小

如下的异常条件与方法区有关:

  • 如果方法区中的内存不能满足分配请求,JVM抛出OutOfMemoryError

运行时常量池(Run-time Constant Pool)#

一个运行时常量池是每个类或接口的类文件中的constant_pool表的运行时表示。它包含了多种类型的常量,范围从编译时可知的数字字面量到必须在运行时才能被解析的方法和域引用。运行时常量池提供一个提供了类似于传统编程语言中符号表的功能,尽管它其中包含比典型的符号表更广泛的数据。

每一个运行时常量池都在JVM的方法区中被分配,一个类或接口的运行时常量池在类或接口被JVM虚拟机创建时被构造。

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

  • 当创建一个类或接口时,如果运行时常量池需要比JVM方法区更多的内存空间,JVM抛出OutOfMemoryError

See §5 (Loading, Linking, and Initializing) for information about the construction of the run-time constant pool.

本地方法栈(Native Method Stack)#

Java虚拟机的实现可以通过使用传统的栈,通俗来讲:“C栈”来支持本地方法。Java虚拟机指令集解释器的实现也可以使用本地方法栈,例如使用C实现(指令集解释器)。
不能加载本地方法或者不依赖传统栈的Java虚拟机实现可以不提供本地方法栈,如果提供了,本地方法栈通常在每个线程创建时传递给对应线程。

这个定义允许本地方法栈是固定大小或者根据计算所需动态扩展或收缩,如果一个本地方法栈是固定大小的,那么这个大小可以在每个线程创建时被独立的选择。

Java虚拟机实现可以提供程序员或用户控制的初始本地方法栈大小,同样地,可变大小的本地方法栈可以控制最大和最小方法栈大小

下面的异常条件与本地方法栈相关:

  1. 如果计算出一个线程需要一个比允许值更大的本地方法栈,JVM抛出StackOverflowError
  2. 如果本地方法栈可以被动态扩展并且已经尝试了扩展但是并没有足够的内存用来扩展,或者在创建一个新线程时没有足够的内存来创建它的本地方法栈,JVM抛出OutOfMemoryError

栈帧(Frame)#

一个栈帧用于存储数据和部分结果,以及执行动态链接,方法返回值和调度异常。

每当一个方法被调用,一个新的栈帧就被创建,每当一个方法调用完成,一个栈帧就被销毁,不论时正常完成还是意外完成(抛出一个未捕获异常)。帧由每个线程的Java虚拟机栈创建新帧时分配,每一个帧有一个自己的本地变量(local variable 又称局部变量)数组,一个自己的操作数栈和一个类的当前方法在运行时常量池中的引用。

一个栈帧可以被扩展出附加的,特定于实现的(implementation-specific)信息,比如调试信息(debugging information)

本地变量数组和操作数栈的大小在编译时被决定,并且与栈帧所关联的方法的代码一起提供。因此栈帧数据结构的大小仅仅依赖JVM的实现,并且这些结构的内存会在方法调用时同步的被分配。

在给定线程的任何一个时刻只有一个栈帧——正在执行的方法的栈帧——是活跃的。这个栈帧被认为是当前栈帧,并且这个方法被认为是当前方法。当前方法被定义的类称作当前类。本地变量和操作数栈的操作通常引用当前栈帧。

Operations on local variables and the operand stack are typically with reference to the current frame.

当一个方法调用了另一个方法或者方法完成时,这个方法的栈帧不再是当前栈帧。当一个方法被调用,一个新的栈帧被创建并且当控制权转移到这个新方法时,该栈帧变成当前栈帧。在方法返回时,当前栈帧回传它的方法调用的结果给上一个栈帧(如果有的话)。然后当前栈帧将被丢弃,因为前一个栈帧变成了当前栈帧。

注意一个线程创建的栈帧是那个线程私有的,并且不能被任何其它线程所访问。

本地变量(Local Variables)#

每一个栈帧包含一个存储本地变量的数组,这个数组的长度在编译时被确定并且由一个二进制形式的类或接口中该栈帧关联的方法所提供。

一个单独的本地变量可以保存一个booleanbytecharshortintfloatreferencereturnAddress类型的值,longdouble类型的值需要由一对儿变量保存。

本地变量通过索引被找到,第一个本地变量的索引是0。当且仅当一个整数比本地变量数组的大小小0和1时,这个整数才被认为是本地变量数组的索引。

An integer is considered to be an index into the local variable array if and only if that integer is between zero and one less than the size of the local variable array.

一个longdouble类型的值占据两个连续的本地变量,这样的值通过较小的那个索引来找到。比如一个double值存储在本地变量数组中索引为n的位置,那么实际上它占据了nn+1。这种情况下,不能从索引n+1处加载变量,但是可以向其中存储,尽管这样做会让局部变量n的内容失效。

JVM不需要n是偶数。直观地说,longdouble不需要再本地变量数组中64位对齐。实现者可以自由的决定合适的方法来使用两个局部变量来表示这种值。

Implementors are free to decide the appropriate way to represent such values using the two local variables reserved for the value.

JVM使用本地变量来接收方法调用的参数。在类方法调用中,任何参数传递进去的参数都从本地变量0开始被连续的存储在本地变量中。在实例方法调用中,本地变量0总是被用于传递方法被调用的实例的对象(在Java编程语言中的this)。任何参数随后从局部变量1开始的连续局部变量中传递。

操作数栈#

每一个栈帧包含一个后进先出(LIFO)栈,称为操作数栈。操作数栈的最大深度在编译时被决定并且由关联到栈帧的方法中的代码提供。

在上下文明确的情况下,我们有时会把当前栈帧的操作数栈称为操作数栈(省略当前栈帧几个字)。

当包含它的栈帧被创建时,操作数栈是空的。JVM提供了从本地变量或域中加载常量或值到操作数栈中的指令。其它JVM指令从操作数栈中拿出操作数,运算它们,然后将结果重新放入操作数栈中。操作数栈也被用于准备传入方法的参数和接收方法的返回值。

例如,iadd指令将两个int值相加。它要求要进行加法的两个int值是操作数栈顶的两个值,这两个值由之前的指令push到操作数栈中。这两个int值都被从操作数栈中弹出,它们相加了,并且它们的和被压回操作数栈顶。子计算可以嵌套在操作数栈上,从而产生可由包含计算使用的值(这里我猜说的是带括号的计算这种形式)

Subcomputations may be nested on the operand stack, resulting in values that can be used by the encompassing computation.

操作数栈的每一个条目都可以保存任意JVM类型的值,包括longdouble

操作数栈中的值必须以适合它们类型的方式进行操作。比如,下面两种操作是不可能的:压入两个int值然后将它们看作long或者压入两个float然后使用iadd将它们相加。一小部分JVM指令(dupswap指令)像操作原始值一样操作运行时数据区,而不关心它们特定的类型。这些指令被以一种不能够修改或破坏独立的值的方式被定义。这些对操作数栈操作的限制是在class文件校验阶段被强制执行的。

在任一时间,一个操作数栈都有一个关联的深度,longdouble占用两个单元的深度,其它值占用一个单元的深度。

动态链接#

为了支持方法代码的动态链接,每一个栈帧包含一个当前方法的类型在运行时常量池中的引用。方法的类文件代码会通过符号引用(symbolic references)来引用要调用的方法和访问的变量。动态链接将这些符号方法引用翻译成实际的方法引用,在必要的时候执行类加载以解析尚未定义的符号,并且将变量访问翻译成与这些变量运行时位置相关的存储结构中对应的偏移量。

这个方法和变量的延迟绑定(late binding)会使方法中使用的其他类中的更改不太可能破坏方法的代码。

方法调用正常结束#

如果一个调用没有引起一个被抛出的异常,不管是JVM直接抛出的还是由于作为执行一个特定的throw语句所产生的结果,该方法的结束都是正常结束。如果当前方法的调用正常结束,一个值将被返回给调用者方法(invoking method)。当被调用方法执行了任何一个返回指令时就会发生这种情况,返回指令的选择必须和返回的值的类型相对应(如果有的话)。

这个示例中的当前栈帧被用来恢复调用者的状态,包括它的本地变量和操作数栈,并适当增加调用者的程序计数器以跳过本次方法调用指令。
然后,调用者方法就在它的栈帧中继续正常执行,之前方法的返回值被压到调用者方法的操作数栈中。

方法调用意外结束#

如果一个方法中的JVM指令的执行导致了一个JVM抛出了一个异常,并且这个异常没有被method处理,这个方法调用就是意外结束的。执行一个athrow指令也会导致一个一场被显式的抛出并且如果异常没被当前方法捕获,结果就是方法调用异常结束。一个异常结束的方法调用永远不会返回一个值给它的调用者。

对象表示#

JVM不强制任何特定的对象内部结构。

在一些Oracle的JVM实现中,一个类的实例的引用是一个句柄的指针,而句柄本身就是一对指针:一个指向一个包含对象的方法的表和一个指向代表对象类型的Class对象的指针,另一个指向对象数据在堆中被分配的内存

In some of Oracle’s implementations of the Java Virtual Machine, a reference to a class instance is a pointer to a handle that is itself a pair of pointers: one to a table containing the methods of the object and a pointer to the Class object that represents the type of the object, and the other to the memory allocated from the heap for the object data.

浮点数算法#

特殊方法#

实例初始化方法#

一个类具有0个或者多个实例初始化方法,每一个通常对应着一个通过Java编程语言编写的构造器。

一个满足下面所有条件的方法就是一个实例初始化方法:

  1. 定义在一个类中(非接口中)
  2. 具有特殊的名字<init>
  3. 它是void

在一个类中,一个叫<init>的非void方法不是一个实例初始化方法,在一个接口中,任何叫<init>的方法都不是一个实例初始化方法。这些方法并不能被JVM虚拟机指令调用并且会在格式检查中被拒绝。

实例初始化方法的定义和使用被JVM所限制。在定义中,方法的access_flags项目和code数组受限。而在使用中,一个实例初始化方法将仅能被invokespecial指令在一个未初始化类的实例上调用。

因为名称<init>并不是一个Java编程语言中合法的标识符,所以它并不能直接被Java编程语言编写的程序所使用

类初始化方法#

一个类或者接口具有最多一个类或接口的初始化方法,并且它们(类和接口)被JVM通过调用这个方法来初始化。

如果一个方法满足下面的所有定义,那么它是一个类或接口的初始化方法:

  1. 它具有特殊的名字<clinit>
  2. 它是void
  3. 在一个版本号在51.0(JAVA7)或更高的类文件中,方法的ACC_STATIC标志被设置并且没有参数

ACC_STATIC的要求在Java SE 7中被引入,不接受参数在Java SE 9中。在一个版本号在50.0及以下的类文件中,一个名字叫<clinit>,返回void的方法就可以被认为是类或接口的初始化方法,不论ACC_STATIC是否设置或是它们是否接收参数

类文件中其它叫<clinit>的方法不是类或接口的初始化方法。它们永远不会被JVM本身调用,不能被任何JVM指令调用,并且会被格式检查所拒绝。

因为名字<clinit>并不是一个合法的Java编程语言标识符,所以它不能直接在一个使用Java编程语言编写的程序中被使用

签名多态方法(Signature Polymorphic Methods)#

译者:下面的内容和Java7开始新增的动态方法调用和Java9中出现的变量句柄有关,如果从未学习过相关的知识,不妨跳过这一段。

一个满足下面所有条件的方法是签名多态方法

  1. 被声明在java.lang.invoke.MethodHandle类或java.lang.invoke.VarHandle类中
  2. 具有单个形式的Object[]类型参数
  3. 具有ACC_VARARGSACC_NATIVE标志

Java虚拟在invokevirtual指令中给签名多态方法做了一些特殊处理,为了影响一个method handle(方法句柄)的调用或者影响一个被java.lang.invoke.VarHandle的实例所引用的变量。

一个方法句柄是一个对底层方法、构造器和域或者类似的低级操作的动态强类型且直接可执行的一个引用,具有可选的参数和返回值的转换。一个java.lang.invoke.VarHandle的实例是一个变量或一个变量簇的动态强类型的引用,包括static域、非static域、数组元素或一个堆外数据结构的组件。查看Java SE平台API中的java.lang.invoke包来了解更多信息。

异常#

一个JVM中的异常被表示为一个Throwable或它的任何子类的实例。抛出一个异常会导致从抛出异常的位置立即进行非本地的控制转移。

Throwing an exception results in an immediate nonlocal transfer of control from the point where the exception was thrown.

大多数异常是由于一个发生异常的线程的操作而同步发生的。相对的,一个异步异常可以在一个程序执行的任何时间点发生。JVM抛出异常有如下三种原因:

  • 一个athrow指令被执行
  • 一个反常的执行条件被JVM同步的检测到。这些异常不会在程序中的任意位置抛出,而是在一个指令的执行后同步抛出的:
    • 特定类型的异常:
      • 当指令体现了一个违反Java编程语言语义的一个操作时。比如索引一个数组的界限之外。
      • 在一个程序的加载或链接部分发生了一个错误
    • 导致一些资源达到最大限制,比如使用了太多内存
  • 一个异步异常被抛出因为:
    • ThreadThreadGroup类的stop方法被调用或者
    • 一个JVM实现的内部错误发生
      stop方法可能一个线程调用去影响另一个线程或者在一个特定线程组的所有线程。它们是异步的因为它们可能在其它线程(thread and threads)执行的任意点发生。一个内部错误也被认为是异步的。

一个Java虚拟机可以允许一个少量但有限的执行在异步异常被抛出之前发生。这个延时允许优化的代码在符合Java编程语言语义的情况下检测并抛出这些异常。

未完...

指令集总结#

类库#

JVM必须为Java SE平台的类库实现提供足够的支持。这些类库中的一些类必须和JVM交互才能实现。

类可能需要JVM的如下特殊支持:

  • 反射,如java.lang.reflect包中的类和Class
  • 加载和创建一个类或接口,最明显的例子就是ClassLoader
  • 链接和初始化类或接口,上面所说的例子也适用于这个分类
  • 安全,比如java.security中的类,其它的类似SecurityManager
  • 多线程,比如Thread
  • 弱引用(Weak references),比如java.lang.ref

上面的列表只是用来描述这个问题,它并不全面,详细信息请参阅Java SE平台类库的细节。

公有设计,私有实现#

到目前,这份定义描绘了JVM的一个公共视图:class文件格式和指令集。这些组件对于JVM的硬件独立、操作系统独立和实现独立是至关重要的。实现者可能更愿意将它们视为在每个实现Java SE平台的主机间安全的让程序片段通信的一种手段,而非要严格遵循的蓝图。

理解共有设计和私有实现之间的界限是很重要的。一个JVM实现必须可以能够阅读class文件并且必须准确的实现JVM代码中的语义。实现这一点的一种方式是将该文档作为一个规范并实际的实现该规范。但是对于实现者来说,在本规范的约束下修改或优化实现也是完全可行和理想的。只要class文件格式可以被读取并且维护其中代码的语义,实现者就可以以任何方式来实现这些语义。只要仔细维护正确的外部接口,底层如何运行是实现者随意决定的。

有一些例外:debugger、性能监视器和即时代码编译器可能都需要访问JVM中的元素,这些元素通常被认为是底层的。在适当情况下,Oracle和其它的Java虚拟机实现者和工具供应商合作,为这些工具开发Java虚拟机的通用接口,并在整个行业内推广这些接口。

实现者可以使用这种可灵活性来实现高性能、低内存占用或是可移植的JVM,在给定的实现中,哪些方面更加重要取决于该实现的目标。实现选项的范围包括以下内容:

  • 在加载时或执行时翻译JVM代码为另一个虚拟机的指令集
  • 在加载时或执行时翻译JVM代码为宿主CPU的本地指令

JVM和目标文件格式的精确定义的存在不应该过分的限制实现者的创造力。JVM被设计成支持许多种不同实现,以提供新的和有趣的解决方案,同时也要保持实现之间的兼容性。

posted @   yudoge  阅读(116)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示
主题色彩