第一章 介绍

历史

Java编程语言是一种通用的、并发的、面向对象的语言语言。它的语法类似于C和C++,但它省去了许多使C和C++变得复杂、混乱和不安全的特性。Java平台被开发,最初是用于解决构建网络设备的软件的问题。它被设计为支持多主机架构,并允许软件组件的安全交付。为了满足这些要求,编译后的代码必须满足要在跨网络传输中存活,在任何客户端上操作,并确保客户端是安全运行的目标。

万维网的普及使这些特性更加明显有趣。网络浏览器使数百万人能够以简单的方式上网并且访问丰富的媒体内容。终于有了一种媒介不管你使用的是哪种机器,无论连接的是快速的网络还是慢的调制解调器,看到和听到的内容基本上是相同的。

Web爱好者很快就发现了由Web的HTML所支持的内容文档格式太有限。HTML扩展,比如表单强调了这些限制,同时明确指出没有浏览器可以包含所有用户想要的功能。可扩展性是答案。

HotJava浏览器首先展示了Java编程语言和平台使程序嵌入在HTML页面成为可能的有趣的特性(译者注:Java Applet)。程序和HTML页面被透明地下载到浏览器中,并且呈现在HTML页面中。在被浏览器接受之前,程序被仔细检查,以确保是安全的。像HTML页面,编译后的程序是独立于网络和主机的。这些程序不管来自哪里或使用哪种机器加载和运行,它们都表现出相同的行为。

与Java平台结合的Web浏览器不再局限于预先确定的功能集。访问者访问包含动态内容的网页可以确保其机器不会被该内容损坏。程序员只需要编写一次程序,就可以在任何提供Java运行时环境的机器上运行(译者注:Write once, run anywhere)。

Java虚拟机

Java虚拟机是Java平台的基石。这是负责硬件和操作系统独立性的技术组件,其编译代码的体积小,且具有保护用户免受恶意程序的侵害的能力。

Java虚拟机是一个抽象的计算机。就像真正的计算一样机器,它有一个指令集,并在运行时操作各种内存区域。使用虚拟机来实现编程语言是相当常见的;最著名的虚拟机可能是UCSD PascalP-Code虚拟机。

Java虚拟机的第一个原型实现,在 Sun Microsystems公司完成, 在软件中模拟了Java虚拟机指令集,该软件由一个类似于当代个人数码设备(PDA)的手持设备托管。Oracle当前的实现模拟了Java虚拟机在移动端,桌面端和服务器端的设备,但Java虚拟机不限定(not assume)任何特定的实现技术、主机硬件或主机操作系统。它不是固有的解释型的( It is not inherently interpreted),但也可以通过将其指令集编译为处理器(silicon CPU)的指令集来实现。也可能可以在微指令(microcode)或者硬件(silicon)中实现。

Java虚拟机对Java编程语言一无所知,仅针对特定的二进制格式,class文件格式。其中class文件包含Java虚拟机指令(或字节码(bytecodes))和符号表等辅助信息。

为了安全起见,对于class文件的代码,Java虚拟机引入了强大的语法和结构约束。然而,任何可以通过合法的class文件表示功能的语言都可以被托管到Java虚拟机(hosted by)。被一种普遍可用的、与机器无关的平台的特性所吸引的开发者,都可以转向Java虚拟机,将其作为他们语言的传递工具(Groovy,Kotlin)。

这里指定的Java虚拟机与Java SE 17平台兼容,并支持 The Java Language Specification, Java SE 17 Edition中指定的Java编程语言。

内容组织

第2章概述了Java虚拟机架构。

第3章介绍了用Java编程语言编写的代码编译成Java虚拟机指令的过程。

第4章具体说明与硬件和操作系统无关用于表示已编译类和接口的二进制格式的class文件格式。

第5章具体说明Java虚拟机的启动,加载、链接和初始化类和接口。

第6章具体说明Java虚拟机的指令集,以操作码助记符的字母顺序来展示指令。

第7章给出了一个以操作码值为索引的Java虚拟机操作码助记符表。

在第二版的Java Virtual Machine Specification,第2章概述了 Java 编程语言,旨在支持 Java 虚拟机规范,但它本身并不是规范的一部分。在Java Virtual Machine Specification, Java SE 17 Edition(译者注:也就是本手册)中,请读者参阅The Java Language Specification, Java SE 17 Edition来查询Java编程语言相关信息。引用的格式:(JLS§x.y)指出需要这样做的地方。

在第二版的Java虚拟机规范,第8章详细解释了具有共享主存的Java虚拟机线程交互的底层操作。在Java Virtual Machine Specification, Java SE 17 Edition中,读者可以参考The Java Language Specification, Java SE 17 Edition的第17章有关线程和锁的信息。第17章反映了出自于JSR133专家组的The Java Memory Model and Thread Specification

符号

在整个规范中,我们指出的类和接口取自Java SE平台API。当我们引用一个类或接口时(除了那些声明在示例中的),使用单个标识符N,其指的是java.lang包中的类或接口。在java.lang包以外的类或者接口,我们使用全限定名(fully qualified name)来指明。

每当我们引用一声明在java或它的任何子包中的类或者接口时,预期的引用是那个类或者接口被启动类加载器加载(§5.3.1)。

每当我们引用一个名为java的包的子包时,预期的引用是由启动类装加载器决定的。

本规范中字体的使用如下:

  • 等宽字体用于Java虚拟机数据类型、异常、错误、class文件结构、Prolog代码和Java代码片段。
  • 斜体是用于Java虚拟机的“汇编语言”,其操作码和操作数,以及Java虚拟机的运行时数据区域中的项。它也用于介绍新的术语和简单的强调。

非规范性信息,旨在澄清规范,使用以下格式表示:

这是非规范性信息。 它提供直觉、基本原理、建议、示例等。

反馈

读者可以报告Java Virtual Machine Specification中的技术错误和歧义,反馈至jls-jvms-spec-comments@openjdk.java.net(译者注:由于译者水平有限,此翻译可能存在不准确,反馈时请自行阅读原版规范确定无误后再反馈)。

关于javac(Java编程语言的参考编译器)生成和操作class文件的问题可以发送到compilerdev@openjdk.java.net(译者注:由于译者水平有限,此翻译可能存在不准确,反馈时请自行阅读原版规范确定无误后再反馈)。

JVM的结构

这个文档具体说明了一个抽象机器。它没有描述任何特定的Java虚拟机的实现。

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

本规范中对Unicode的所有引用都是关于The Unicode Standard, Version 13.0,可在 https://www.unicode.org/ 获得。

class文件格式

由 Java 虚拟机执行的编译代码表示为一种独立于硬件和操作系统的二进制格式,通常(但不是必须)存储在一个文件中,称为class文件格式。 class文件格式精确定义类或接口的表示,包括细节,例如在特定于平台的目标文件中可能被视为理所当然的字节顺序(Byte Ordering)。

第 4 章,”class文件格式“,详细介绍了class文件格式。

数据类型

与 Java 编程语言一样,Java 虚拟机在两种类型上进行操作:原子类型(primitive types)引用类型(reference types)。相应地,有两种类型的值可以被存储在变量中,作为参数传递,作为方法返回值被返回,并可以对其进行操作:原子类型的值(primitive values)引用类型的值(reference values)

Java 虚拟机期望几乎所有类型检查都在运行前完成,通常这是由编译器完成的,而不必由 Java 完成虚拟机本身完成。原子类型的值不需要被标记(tagged)或以其他方式检查以确定它们在运行时的类型,或与引用类型的值进行区分。取而代之的是Java虚拟机的使用那些操作在特定类型上的指令来区分操作数类型。例如,iaddladdfadddadd 都是 Java 虚拟机将两个数值相加并产生数值结果的指令,但每一个都是专门针对其操作数类型:分别为 intlongfloatdouble。对于Java虚拟机指令集支持的类型的总结,请参阅 §2.11.1。

Java 虚拟机包含对对象的显式支持。一个对象是动态分配的类实例或数组。对对象的引用被认为时 Java 虚拟机引用(reference)类型。reference类型的值可以被认为是指向对象的指针。总是通过reference类型的值来操作、传递和测试对象。

原子类型

Java 虚拟机支持的原子数据类型是数字类型(numeric types)、布尔类型(boolean type)(第 2.3.4 节)和 returnAddress 类型(第 2.3.3 节)。

数字类型由整数类型(integral types)(第 2.3.1 节)和浮点类型(floating-point types)组成(§2.3.2)。

整数类型包括:

  • byte,它的值时8位有符号二进制补码整数,默认值为0。
  • short,它的值时16位有符号二进制补码整数,默认值为0。
  • int,它的值时32位有符号二进制补码整数,默认值为0。
  • long,它的值时64位有符号二进制补码整数,默认值为0。
  • char,它的值时16位无符号整数表示的在基本多文种平面Unicode码点(code point),使用UTF-16编码,默认值是空码点(\u0000)。

浮点类型包括:

  • float它的值与IEEE 754 binary32格式的相对应,默认值是正数0(+0)
  • double,它的值与IEEE 754 binary64格式的相对应,默认值是正数0(+0)

boolean类型的值编码真值是truefalse,默认值是false

第一版的The Java Virtual Machine Specification没有将boolean类型视为一种Java虚拟机类型。然而,boolean值在Java虚拟机中有受限的支持。第二版的The Java Virtual Machine Specificationboolean视为一种类型来阐明这个问题。

returnAddress 类型的值是指向 Java虚拟机指令的操作码的指针。 在原子类型中,只有 returnAddress 类型不是直接与 Java 编程语言类型相关联。

整数类型和值

Java虚拟机的整数类型的值为:

  • 对于byte,范围为-128~127(\(-2^7\)~\(2^7-1\)),包含-128和127
  • 对于short,范围为-32768~32767(\(-2^15\)~\(2^15-1\)),包含-32768和32767
  • 对于int,范围为-2147483648 ~2147483647(\(-2^31\)~\(2^31-1\)),包含-2147483648和2147483647
  • 对于long,范围为-9223372036854775808~9223372036854775807(\(-2^63\)~\(2^63-1\)),包含-9223372036854775808和9223372036854775807
  • 对于char,范围为0~65535,包含0和65535

浮点类型和值

浮点类型有floatdouble,它们在概念上分别和IEEE 754 中的32位binary32和64位binary64浮点数格式的值和操作相关联(JLS§1.7)。

在Java SE 15及之后的版本中,Java虚拟机使用IEEE 754的2019版标准。在Java SE 15之前,Java虚拟机使用的是1985版的IEEE 754标准,其中binary32格式被称为单精度(single format),binary64格式被称为双精度(double format)。

IEEE 754不仅包括由符号和绝对值(magnitude)组成的正数和负数,还有正零和负零,正无穷大和负无穷大(infinities),和特殊的非数值的值(Not-a-Number)(以下简称NaN)。NaN值为用于表示某些无效运算的结果,如0除以0。floatdouble类型的NaN常量被预定义为Float.NaNDouble.NaN

有限的非零的浮点数类型的值可以被表示为\(s\cdot m \cdot 2^{(e-N+1)}\) ,这里的:

  • \(s\) 是 +1 或者 -1,
  • \(m\) 是一个小于 \(2^N\) 的正整数,
  • \(e\) 是一个整数,满足\(E_{min}\le e \le E_{max}\) ,其中 \(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。

如果\(m \ge 2^{N-1}\),这种形式的表示称为normalized;否则,被称为subnormal。如果一个浮点类型的值不能够以\(m \ge 2^{N-1}\)这种形式表示,则称该值为非规格数(subnormal number),因为它的绝对值(magnitude)低于最小的规格数(normalized value)。(译者注:具体的术语参考wiki,这里翻译来自https://www.jianshu.com/p/43b1b09f27f4)

floatdouble中\(N\) 和 \(K\) 参数的约束(以及有它们衍生的 \(E_{min}\) 和 \(E_{max}\))总结在表2.3.2-A中:

表2.3.2-A. 浮点数参数
参数 float double
N 24 53
K 8 11
E m a x +127 +1023
E m i n -126 -1022

除了NaN,浮点值是有序的(ordered)。从小到大进行排列,分别是负无穷,负有限非零值,正零和负零,正有限非零值,正无穷。

IEEE 754允许它的binary32和binary64浮点格式有多个不同格式的NaN。然而,Java SE平台通常将一个给定的浮点类型的NaN值看作为一个单一的规范值(a single canonical value),因此,该规范通常引用任意NaN,就像引用一个规范值。

在IEEE 754下,用non-NaN参数进行浮点操作可能会产生NaN结果。IEEE 754指定了一组NaN位模式,但没有强制要求哪个特定的NaN位模式被用来表示NaN结果;这是留给了硬件体系结构。程序员可以创建具有不同位模式的NaN编码,例如,回溯性诊断信息(A programmer can create NaNs with different bit patterns to encode, for example, retrospective diagnostic information.)。这些NaN值可以是使用Float.intBitsToFloatDouble.longBitsToDouble创建,float对应Float.intBitsToFloatdouble对应Double.longBitsToDouble。相反,要检查NaN值的位模式,floatdouble类型可以分别使用Float.floatToRawIntBitsDouble.doubleToRawLongBits方法。

正0和负0比较起来是相等的,但是还有其他的运算可以区分它们;例如,用1.0除以0.0得到正无穷,但是用1.0除以-0.0得到负无穷。

NaN是无序的(unordered),因此,如果操作数有一个或两个都是NaN,数值比较和数值相等的测试都为false。特别的,数值相等性测试中值与自身的数值相等的结果为false,当且仅当该值为NaN。数值的不等性测试中,如果任何一个操作数是NaN,测试结果为true

returnAddress类型和值

returnAddress类型被用于Java虚拟机的jsrretjsr_w指令(§jsr、§ret、§jsr_w)。returnAddress类型的值是指向Java虚拟机指令操作码的指针。不像数字型的原子类型,returnAddress类型没有和Java编程语言的任何类型相对应,而且不能够被正在运行的程序修改。

boolean类型

虽然 Java 虚拟机定义了boolean类型,但提供给其的支持非常有限。 没有单独的 Java 虚拟机指令专用于对布尔值的操作。 相反,Java 编程语言中操作在boolean值上的表达式会被编译成使用Java虚拟机的int数据类型。

Java 虚拟机确实直接支持boolean数组。 它的newarray指令(§newarray)允许创建boolean数组。 boolean类型的数组可以使用byte数组的baloadbastore指令来实现访问和修改(§baload, §bastore)。

在 Oracle 的 Java 虚拟机实现中,Java编程语言的boolean数组被编码成Java虚拟机的byte数组,每个boolean元素使用8位(8 bits)来表示。

Java虚拟机对boolean数组进行编码时,使用1表示true,使用0表示false。Java编程语言boolean值被编译器映射到Java虚拟机的int类型的地方,编译器必须使用相同的编码。

引用类型

有三种引用(reference)类型:类类型、数组类型和接口类型。相对应的,它们的值是对动态创建的类实例、或实现接口的类实例或者数组的引用。

数组类型由具有单一维度的组件类型(component type)组成(其长度不是由类型给出的)。数组类型的组件类型本身可以是数组类型。如果,从任何数组类型开始,考虑其组件类型,然后是该类型(如果它也是一个数组类型)的组件类型,等等,最终必须到达组件类型不是数组类型;这个类型则叫做数组的元素类型(element type)。数组类型的元素类型必须是原子类型,或者类类型,或这接口类型。

引用值(reference value)也可以是特殊的空引用(null reference),这个引用没有对象(a reference to no object),这里用null表示。空引用最初没有运行时类型,但可以转换为任何类型。reference类型的默认值为null

此规范不强制要求将null编码为一个特定的值。

运行时数据区域

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

pc寄存器

Java虚拟机可以一次支持许多执行线程(JLS§17)。每个 Java虚拟机线程都有自己的PC(程序计数器)寄存器。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法(§2.6)。如果该方法不是native方法,则PC寄存器包含当前正在执行的Java虚拟机指令的地址。如果当前由线程执行的方法是native方法,则Java 虚拟机的PC寄存器的值是未定义的。Java 虚拟机的PC寄存器足够宽,可以在特定平台上容纳returnAddress或者原生指针(native pointer)。

Java 虚拟机栈

每个 Java 虚拟机线程都有一个私有的 Java 虚拟机堆栈(Java Virtual Machine stack),该虚拟机栈与线程同时创建。一个Java虚拟机栈存储着帧(frames)(§2.6)。Java虚拟机栈类似于传统语言如 C 语言的栈;它保存局部变量和部分结果,并在方法的调用和返回时发挥作用。因为除了对帧的 push 和 pop 操作外,Java 虚拟机栈永远不会被直接操作,所以帧可以在堆上分配。Java 虚拟机栈的内存不需要连续。

在第一版的 Java 虚拟机规范中,Java 虚拟机栈(Java Virtual Machine stack)被称为 Java 栈(Java stack)。

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

Java 虚拟机实现可以让程序员或用户控制 Java 虚拟机栈的初始大小,在动态扩展或收缩 Java 虚拟机栈的情况下,还可以控制最大和最小容量。

以下是与 Java 虚拟机栈相关的异常条件:

  • 如果线程中的运算需要的 Java 虚拟机栈的空间超出允许的空间,Java 虚拟机将抛StackOverflowError
  • 如果 Java 虚拟机栈可以动态地扩展,且扩展时内存不足而影响到了扩展,或者内存不足以创建一个新线程的初始 Java 虚拟机栈,Java 虚拟机抛出一个OutOfMemoryError

Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的堆(heap)。堆是分配所有类实例和数组的内存的运行时数据区域。

堆是在虚拟机启动时创建的。对象的堆存储空间由自动存储管理系统(称为垃圾收集器garbage collector)回收;对象从来不会被显式地回收。 Java 虚拟机不假定自动存储管理系统的特定类型,并且可以根据实现者的系统要求选择存储管理技术。堆可能具有固定尺寸,也可以按计算的要求扩展,如果不需要较大的堆,则可能会收缩。堆的内存不需要连续。

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

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

  • 如果计算需要的堆内存大小比自动存储管理系统所能提供的更多,则 Java 虚拟机会抛出OutofMemoryError

方法区

Java 虚拟机有一个方法区(method area),该方法区在所有 Java 虚拟机线程中共享。该方法区类似于通常所说的编程语言的编译代码的存储区域或类似于操作系统进程中的代码段(“text" segment)。它存储了每个类结构,例如运行时常量池,成员变量(field)和方法数据以及方法和构造函数的代码,包括类和接口初始化以及实例初始化中使用的特殊方法(§2.9)。

方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆(heap)的一部分,但简单的实现可能会选择不对该区域进行垃圾收集或压缩该区域(may choose not to either garbage collect or compact it)。该规范不要求方法区的位置或用于管理编译代码的策略。方法区可以具有固定大小,也可以根据计算的要求进行扩展,如果不需要较大的方法区域,则可能会收缩。方法区的内存不需要连续。

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

以下异常条件与方法区相关联:

  • 如果方法去中的内存无法满足分配需求,则 Java 虚拟机将抛出OutOfMemoryError

运行时常量池

运行时常量池(run-time constant pool)是每一个类或者接口的class文件中的constant_pool表的运行时表示(第4.4节)。它包含多种类型的常量,从编译时已知的数字常量到必须在运行时必须决定的方法和成员变量的引用。运行时常量池具有类似于常规编程语言的符号表的函数,但它包含的数据范围比典型的符号表更宽泛。

每个运行时常量池都是从 Java 虚拟机的方法区(§2.5.4)分配的。当 Java 虚拟机创建类或接口(§5.3)时,会创建类或接口的运行时常量池。

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

  • 创建类或接口时,如果运行时常量池的创建需要的内存大于 Java 虚拟机的方法区域的可用的内存,则 Java 虚拟机将抛出 OutOfMemoryError

有关运行时常量池的创建的信息,请参见§5(加载,链接和初始化)。

本地方法栈

Java 虚拟机的实现可以使用常规的栈,称为“ C 栈”,以支持本地(native)方法(用Java编程语言以外的其他语言编写的方法)。本地方法栈也可以被用于 Java 虚拟机指令集解释器的实现的语言中,如 C 语言。不能加载native方法和不依赖于传统的栈的 Java 虚拟机实现没有必要提供本地方法栈。如果提供,当创建每个线程时,通常会分配本地方法栈。(An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.)

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

Java 虚拟机实现可以提供给程序员或用户对本地方法栈初始大小的控制,以及在本地方法栈大小变动的情况下,可以控制最大和最小方法栈大小的功能。

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

  • 如果线程中的计算需要的本地方法栈比允许的更大,Java 虚拟机将抛出StackOverflowError
  • 如果可以动态地扩展本地方法栈,并且尝试进行本地方法栈扩展,但是内存不足,或者没有足够的内存为新线程创建初始的本地方法堆栈,那么 Java 虚拟机将抛出OutOfMemoryError

栈帧(frames)

栈帧用于存储数据和部分结果,以及执行动态链接,方法返回值以及异常处理(dispatch exception)。

每次调用方法时都会创建一个新的栈帧。方法调用完成时栈帧会被销毁,无论该完成是正常的还是发生意外(抛出一个没有捕获的异常)。创建栈帧的线程在 Java 虚拟机栈上为栈帧分配空间(§2.5.2)。每个栈帧都有自己的本地变量(§2.6.1),其自己的操作数栈(§2.6.2),以及当前方法所在的类的运行时常数池(§2.5.5)的引用。

栈帧可以有额外的实现特定的信息作为扩展,如调试信息。

本地变量组和操作数栈的大小在编译时确定,并与方法的栈帧的代码(第4.7.3节)一起提供。因此,栈帧数据结构的大小仅取决于 Java 虚拟机的实现,并且这些数据结构所需的内存可以在方法调用的同时进行分配。

任何时候,在指定的线程的控制下,只有一个栈帧,即执行方法的栈帧,是活跃的状态。该栈帧称为当前栈帧(current frame),其方法称为当前方法(current method)。定义当前方法的类是当前类(current class)。对本地变量和操作数栈的操作通常都是指对当前栈帧的操作。

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

请注意,由线程创建的栈帧是该线程的本地栈帧,任何其他线程都无法引用。

局部变量

每个栈帧(§2.6)包含称为局部变量(local variables)的变量表(an array of variables)。栈帧的局部变量表(local variable array 译者注:应该就是方法的所有局部变量)的长度在编译时确定,并在类或接口的二进制表示中提供,二进制表示中也有和方法的栈帧相关联的代码(§4.7.3)。

单个局部变量可以容纳booleanbytecharshortintfloatreferencereturnAddress类型的值。一对局部变量可以容纳longdoubleA pair of local variables can hold a value of type long or double)。

局部变量通过索引进行定位。第一个局部变量的索引为零。当且仅当一个整数介于零和局部变量表的长度减一时,该整数被认为是局部变量数组中的索引。

longdouble类型的值占据了两个连续的局部变量。这样的值只能使用较小的索引来定位。例如,一个存储在局部变量表中并且索引为 n 的double类型的局部变量实际上占据了索引为 n 和 n+1 的两个局部变量;但是,索引 n+1 处的局部变量无法加载。但它可以进行存储。但是,这样做会使局部变量 n 的内容无效。

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

Java 虚拟机在方法调用上使用局部变量来传递参数。在类方法(class method)调用上,任何参数均以从局部变量 0 开始的连续局部变量传递。在实例方法(instance method)调用上,局部变量 0始终用于传递对实例方法被调用的对象的引用(在 Java 变成语言中用 this 表示)。之后的任何参数的传递使用的是从局部变量 1 开始的连续的局部变量。

操作数栈

每个栈帧(第2.6节)包含一个名为操作数栈(operand stack)的先进后出(last-in-first-out, LIFO)的栈。栈帧的操作数栈的做大深度在编译时决定,并与方法栈帧的相关代码一起提供(第4.7.3节)。

在上下文清楚的地方,我们有时将当前栈帧的操作数栈称为操作数栈。

当创建包含该操作数栈的栈帧时,该操作数栈为空。 Java 虚拟机提供指令来加载常量、局部变量的值或者是成员变量到操作数栈中。其它 Java 虚拟机指令从操作数栈中取出操作数,对其进行操作,然后将结果压回操作数堆栈(push the result back onto the operand stack)。操作数栈还用于准备要传递到方法的参数和接收方法结果。

例如,iadd 指令(§iadd)将两个int值进行相加。它要求被进行相加操作的两个int值是操作数栈顶的两个值,这两个值是被之前的指令压入栈中的。两个int值都从操作数栈中弹出。它们进行相加操作,并将其总和压回操作数栈。子计算可以嵌套在操作数栈上,从而导致包含计算可以使用的值。(Subcomputations may be nested on the operand stack, resulting in values that can be used by the encompassing computation.)

操作数堆栈上的每个条目(entry)都可以容纳任何 Java 虚拟机类型的值,包括longdouble类型的值。

操作数堆栈中的值必须以适合其类型的方式进行操作。例如,不可以将两个int值压入操作数栈然后将它们视为一个long值,也不可以将两个float值压入操作数栈然后使用 iadd 指令来将它们进行相加。少数 Java 虚拟机指令(dup指令(§dup)和swap指令(§swap))在运行时数据区域作为原始值操作,而无需考虑其特定类型;这些指令的定义为不能用于以修改或分解单个值的方式来进行操作。这些对操作数栈操作的限制是通过class文件验证(第4.10节)强制执行的。

在任何时间点,一个操作数栈具有一个相关联的深度,其中longdouble的值构成了两个单位的深度,并且任何其他类型的值都贡献一个单位的深度。

动态链接

每个栈帧(§2.6)包含对当前方法对应的类型的运行时常量池(§2.5.5)的引用,以支持方法代码的动态链接(dynamic linking)。方法的class文件代码是指通过符号引用被调用的方法和通过符号引用被访问的变量。动态链接将这些符号方法引用转化为具体方法引用,根据需要加载类,以解决尚未定义的符号,并将变量访问转换为与这些变量的运行时位置相关的存储结构中的正确的偏移量。

方法和变量的这种后期绑定使其他类的更改方法在方法中使用的可能性较小。(This late binding of the methods and variables makes changes in other classes that a method uses less likely to break this code.)

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

当此次调用没有抛出异常方法调用正常(completes normally)完成,不管该异常是直接从 Java 虚拟机抛出或者是从显式的 throw 语句抛出。如果当前方法的调用正常完成,则可以将值返回到调用方法。当调用方法执行返回指令之一(§2.11.8)时,就会发生这种情况,其选择必须符合返回值的类型(如果有返回值)。

在这种情况下,使用当前栈帧(§2.6)来恢复调用者的状态,包括其局部变量和操作数栈,调用者的程序计数器适当地递增,以跳过方法调用指令。然后,将返回的值(如果有)压入该栈帧的操作数栈,调用方法正常在其栈帧中执行。

方法调用发生意外(Abrupt Method Invocation Completion)

当此次调用造成 Java 虚拟机抛出异常并且异常没有被处理时方法调用发生意外(§2.10)。执行athrow指令(§athrow)也会导致异常被显式抛出,如果当前方法未捕获异常,会导致方法调用发生意外。意外完成的方法调用永远不会将返回值返回给其调用者。

对象的表示

Java虚拟机并不强制要求对象具有任何特定的内部结构。

在一些Oracle的 Java 虚拟机实现中,类实例的引用是指向句柄(handle)的指针,句柄本身是一对指针:一个指向包含对象的方法和表示该对象类型的Class对象的指针的表,另一个指向在堆上为对象的数据分配的内存。