Java Virtual Machine Specification Java SE 7 中文版
Java Virtual Machine Specification Java SE 7 中文版
(Java SE 7 版)
原文版本. 2011/07/28
译文版本. 2011/11/13
作者:
译者:
周志明([email protected])
吴璞渊([email protected])
冶秀刚([email protected])
Java 虚拟机规范 — 译者序
译者序
《Java 虚拟机规范(Java SE 7 版)
》终于完成并在 7 月份正式发布。对于想了解 Java 虚拟机
门外。
多月的时间里共同完成了其余章节的翻译和校对。
度吻合,但是随着技术的发展,高性能虚拟机真正的细节实现方式已经渐渐与虚拟机规范所描述的
内容产生了越来越大的差距。原作者也在书中不同地方反复强调过:虚拟机规范中所提及的“Java
虚拟机”皆为虚拟机的概念模型而非具体实现。实现只要保证与概念模型最终等效即可,而具体实
现的方式无需受概念模型束缚。因此通过虚拟机规范去分析程序的执行语义问题(虚拟机会做什么)
时,但分析程序的执行行为问题(虚拟机是怎样做的、性能如何)则意义不大,如需对具体虚拟机
实现进行调优、性能分析等,我推荐在本书基础上继续阅读《Java Performance》和《Oracle
在翻译过程中,我们尽最大努力保证作品的准确性和可读性,力求在保证语义准确的前提下,
专有技术名词、偏僻词中用括号保留了原文、专门在多处读者理解起来可能有困难的地方,添加了
“译者注”加以解释。
囿于我们的水平和写作时间,书中难免存在不妥之处,大家如有任何意见或建议都欢迎通过以
下邮件地址与我联系:[email protected]。本书的勘误与最新版本可以在以下网址中获取:
http://www.icyfenix.com/jvms_javase7_cn/
第 2 页 / 共 387 页
Java 虚拟机规范 — 译者序
最后,请允许我再介绍一下本书三位译者的技术背景与分工:
吴璞渊(wupuyuan.iteye.com):就职于西门子,偏向程序和工作流设计,喜好 Java 各
冶秀刚(langyu.iteye.com):思科平台工程师,从事分布式系统的研究与开发,爱好 Java
平台技术且正在努力成长中。在本书翻译工作中负责第 5 章及第 4 章的 9 至 11 节。
周志明
2011 年 11 月 2 日
第 3 页 / 共 387 页
Java 虚拟机规范 — 版权声明
版权声明
1. 本翻译工作完全基于个人兴趣爱好以及学术研究目的,不涉及出版或任何其他商业行为。本次
2. 译者曾经尝试邮件联系过原文作者,但是一直未获得到回复。根据我国著作权法第 22 条规定,
以教学、科研为目的,可以不经著作权人许可翻译其已经发表的作品,不向其支付报酬,但应
当指明作者姓名、作品名称,且不得出版发行。因此本译文的传播,必须严格控制在学习与科
学研究范围内,任何人未经原文作者和译者同意,不得将译文的全部或部分用于出版或其他商
业行为。
3. 在符合第 2 条的前提下,任何人都可任意方式传播、使用本译文的部分或全部内容,无须预
先知会译者,只要保留作、译者联系信息即可。如果需要进行商业使用,则必须到原作者和译
者的授权。
附原文版权声明如下:
Specification: JSR-000924 Java™ Virtual Machine Specification ("Specification")
Version: 7
Status: Final Release
Release: July 2011
Copyright 2011 Oracle America, Inc. and/or its affiliates. All rights reserved.
500 Oracle Parkway M/S 5op7, California 94065, U.S.A
2. License for the Distribution of Compliant Implementations. Oracle also grants you a
perpetual, non-exclusive, non-transferable, worldwide, fully paid-up, royalty free,
第 4 页 / 共 387 页
Java 虚拟机规范 — 版权声明
limited license (without the right to sublicense) under any applicable copyrights or,
subject to the provisions of subsection 4 below, patent rights it may have covering the
Specification to create and/or distribute an Independent Implementation of the
Specification that: (a) fully implements the Specification including all its required
interfaces and functionality; (b) does not modify, subset, superset or otherwise extend
the Licensor Name Space, or include any public or protected packages, classes, Java
interfaces, fields or methods within the Licensor Name Space other than those
required/authorized by the Specification or Specifications being implemented; and (c)
passes the Technology Compatibility Kit (including satisfying the requirements of the
applicable TCK Users Guide) for such Specification ("Compliant Implementation"). In
addition, the foregoing license is expressly conditioned on your not acting outside its
scope. No license is granted hereunder for any other purpose (including, for example,
modifying the Specification, other than to the extent of your fair use rights, or
distributing the Specification to third parties). Also, no right, title, or interest in
or to any trademarks, service marks, or trade names of Oracle or Oracle's licensors is
granted hereunder. Java, and Java-related logos, marks and names are trademarks or
registered trademarks of Oracle in the U.S. and other countries.
3. Pass-through Conditions. You need not include limitations (a)-(c) from the previous
paragraph or any other particular "pass through" requirements in any license You grant
concerning the use of your Independent Implementation or products derived from it. However,
except with respect to Independent Implementations (and products derived from them) that
satisfy limitations (a)-(c) from the previous paragraph, You may neither: (a) grant or
otherv wise pass through to your licensees any licenses under Oracle's applicable
intellectual property rights; nor (b) authorize your licensees to make any claims
concerning their implementation's compliance with the Specification in question.
under subparagraph 2 above, where the infringement of such claims can be avoided in a
technically feasible manner when implementing the Specification such license, with respect
to such claims, shall terminate if You initiate a claim against Oracle that its making,
having made, using, offering to sell, selling or importing a Compliant Implementation
infringes Your patent rights.
This Agreement will terminate immediately without notice from Oracle if you breach the
Agreement or act outside the scope of the licenses granted above.
DISCLAIMER OF WARRANTIES
THE SPECIFICATION IS PROVIDED "AS IS". ORACLE MAKES NO REPRESENTATIONS OR WARRANTIES,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT (INCLUDING AS A CONSEQUENCE OF ANY
PRACTICE OR IMPLEMENTATION OF THE SPECIFICATION), OR THAT THE CONTENTS OF THE SPECIFICATION
ARE SUITABLE FOR ANY PURPOSE. This document does not represent any commitment to release
or implement any portion of the Specification in any product. In addition, the
Specification could include technical inaccuracies or typographical errors.
LIMITATION OF LIABILITY
TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL ORACLE OR ITS LICENSORS BE LIABLE
FOR ANY DAMAGES, INCLUDING WITHOUT LIMITATION, LOST REVENUE, PROFITS OR DATA, OR FOR
SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED IN ANY WAY TO YOUR HAVING,
IMPLEMENTING OR OTHERWISE USING THE SPECIFICATION, EVEN IF ORACLE AND/OR ITS LICENSORS
HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
You will indemnify, hold harmless, and defend Oracle and its licensors from any claims
arising or resulting from: (i) your use of the Specification; (ii) the use or distribution
of your Java application, applet and/or implementation; and/or (iii) any claims that later
versions or releases of any Specification furnished to you are incompatible with the
第 6 页 / 共 387 页
Java 虚拟机规范 — 版权声明
REPORT
If you provide Oracle with any comments or suggestions concerning the Specification
("Feedback"), you hereby: (i) agree that such Feedback is provided on a non-proprietary
and non-confidential basis, and (ii) grant Oracle a perpetual, non-exclusive, worldwide,
fully paid-up, irrevocable license, with the right to sublicense through multiple levels
of sublicensees, to incorporate, disclose, and use without limitation the Feedback for
any purpose.
GENERAL TERMS
Any action related to this Agreement will be governed by California law and controlling
U.S. federal law. The U.N. Convention for the International Sale of Goods and the choice
of law rules of any jurisdiction will not apply. The Specification is subject to U.S. export
control laws and may be subject to export or import regulations in other countries. Licensee
agrees to comply strictly with all such laws and regulations and acknowledges that it has
the responsibility to obtain such licenses to export, re-export or import as may be required
after delivery to Licensee. This Agreement is the parties' entire agreement relating to
its subject matter. It supersedes all prior or contemporaneous oral or written
communications, proposals, conditions, representations and warranties and prevails over
any conflicting or additional terms of any quote, order, acknowledgment, or other
communication between the parties relating to its subject matter during the term of this
Agreement. No modification to this Agreement will be binding, unless in writing and signed
by an authorized representative of each party.
第 7 页 / 共 387 页
Java 虚拟机规范 — 目录
目录
译者序 ................................................................ 2
版权声明 ............................................................... 4
目录 ..................................................................8
前言 .................................................................14
第二版说明 ......................................................... 15
第 1 章 引言 ........................................................... 18
1.1 简史 .......................................................... 18
1.4 说明 .......................................................... 20
2.6 栈帧 .......................................................... 31
2.10 异常 ......................................................... 38
2.11.10 同步 .................................................. 49
2.12 类库 ......................................................... 50
3.9 数组 .......................................................... 69
3.14 同步 ......................................................... 81
3.15 注解 ......................................................... 82
4.3.4 签名 .................................................... 94
CONSTANT_InterfaceMethodref_info 结构 .......................... 99
第 10 页 / 共 387 页
Java 虚拟机规范 — 目录
第 11 页 / 共 387 页
Java 虚拟机规范 — 目录
第 5 章 加载、链接与初始化 ..............................................167
第 13 页 / 共 387 页
Java 虚拟机规范 — 前言
前言
程序员来说都是必不可少的。
关系就犹如一份建筑蓝图与一间具体房屋之间的关系一样。Java 虚拟机具体实现(包括任何公司
的 Java 虚拟机实现)必须包括本规范所描述的内容,但是除了少数绝对必要的地方外,本规范中
我们希望这个规范至少能作为一个“实验室”版本的虚拟机实现的完整描述。如果你正在考虑
虚拟机规范兼容性。
令大家可通过互联网站访问到本书。
做出的编辑工作。读者对本书网上在线版草稿和印刷版草稿所提出诸多意见和建议,也对本书的质
的审查、评价和指导意见,这些宝贵信息都对本书定稿有很大的帮助。
第 14 页 / 共 387 页
Java 虚拟机规范 — 前言
第二版说明
1.2),它还包括了许多对原版的修正并且在不改变规范内容逻辑下,使描述变得更加清晰。我们
也尝试调整了规范中的字体样式、勘误(希望勘误不会导致新的错误)以及对规范中模糊的部分增
间不一致的内容。
我们很感谢所有为我们梳理过第一版规范,并指出问题的读者,特别感谢以下个人和团体,他
们指出的问题甚至是还直接提供的修改意见:
场景编写了兼容性测试用例。在这个过程中,发现了许多处原版规范中不清晰和不完整的内容。
工作有很大帮助。
我们在第二版中合并技术变更的同时,改进了规范的可读性和内容的布局排版,
和《Java 语言规范》之间的描述差异问题都极大地完善了本书。
Tim Lindholm
Frank Yellin
Java SE 7 版说明
处。
语言特性,譬如泛型和变长参数方法等。
容将会在本书的第四章中介绍①。
作出的承诺:
“在未来,我们会对 Java 虚拟机进行适当的扩展,以便更好的支持其他语言运行于
的基础架构由 John Rose 以及 JSR 292 专家组成员:Ola Bini、 Rémi Forax、Dan Heidinga、
Thalinger 作出了其他贡献。
①
译者注:由于原文涉及到大量方法原型和细节,译者认为翻译成中文意义不大。所以介绍此内容的 4.10.1
小节“类型检查验证”是本中文译本中唯一没有翻译的内容。读者可参考英文版 Java 虚拟机规范。
第 16 页 / 共 387 页
Java 虚拟机规范 — 前言
Java 虚拟机能够拥有大家认为理所当然的高效执行性能,他们实在是居功至伟。
描述的内容是可测试并且是已测试的。
Gilad Bracha
Alex Buckley
Java 平台组,Oracle
第 17 页 / 共 387 页
Java 虚拟机规范 — 第 1 章 引言
第 1 章 引言
1.1 简史
解决基于网络的消费设备上的软件开发问题,它在设计上就考虑到要支持部署在不同架构的主机
上,并且不同组件之间可以安全地交互。面对这些需求,编译出来的本地代码必须解决不同网络间
传输问题、可以操作各式各样的客户端,并且还要代码在这些客户端上能安全正确地运行。
万维网的盛行伴随发生了一些十分有趣的事情:Web 浏览器允许数以百万计的用户共同在网
上冲浪,通过很简单的方式访问丰富多样的内容。用户冲浪所使用的设备并不是其中的关键,它们
仅仅是一种媒介,无论你的机器性能如何,无论你使用高速网络还是慢速的 Modem,这些外界因
素本质上与你所看到、听到的内容没有任何关系。
的一些扩展应用,譬如网页表单,让这些限制显得更加明显。显而易见的,没有任何浏览器能够承
诺它可以提供给用户所需要的全部特性,扩展能力将是解决这个问题的唯一答案。
览器中。而在浏览器获取这些代码之前,它们已被严谨地检查过以保证它们是安全的。与 HTML 语
们执行时都能表现出一致的行为。
带有 Java 技术支持的网页浏览器将不再受限于它本身所提供的功能。浏览网页的用户可以放
心地假定在他们机器上运行的动态内容不会损害他们的机器。软件开发人员编写一次代码,程序就
的保护屏障。
Java 虚拟机可以看作是一台抽象的计算机。如同真实的计算机那样,它有自己的指令集以及
各种运行时内存区域。使用虚拟机来实现一门程序设计语言有许多合理的理由,业界中流传最为久
代码执行方式,它不强求使用解释器来执行程序,也可以通过把自己的指令集编译为实际 CPU 的
指令来实现,它可以通过微代码(Microcode)来实现,或者甚至直接实现在 CPU 中。
表,还有一些其他辅助信息。
产品交付媒介。
1.3 各章节提要
本书中其余章节的结构与大意如下:
表示编译后的类和接口。
①
译者注:由加州大学圣地亚哥分校(University of California, San Diego,UCSD)于 1978 年
发布的高度可移植、机器无关的、运行 Pascal 语言的虚拟机。
第 19 页 / 共 387 页
Java 虚拟机规范 — 第 1 章 引言
Java 虚拟机规范,但它本身并不属于规范的一部分。
获取这部分信息,如在本文中有需要引用这些信息的地方,将使用类似于“(JLS §x.y)”的形
式来表示。
这部分内容,读者参考上述规范来获取关于线程与锁的信息。
1.4 说明
要描述其他包中的类或接口,我们将会使用全限定名。
包时,就意味着这个包是由引导类加载器所定义的。
在本书中,下面两种字体含义为②:
这种字体用于代码样例
运行时区域中的项目,有时也被用来说明一些新的条目和需要强调的内容。
①
译者注:《Java Memory Model and Thread Specification》:
http://www.jcp.org/en/jsr/summary?id=133
②
译者注:由于中文和英文语言的差异,字体使用上译本无法完全跟随原文的使用方式。
第 20 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
第 2 章 Java 虚拟机结构
本规范描述的是一种抽象化的虚拟机的行为,而不是任何一种(译者注:包括 Oracle 公司
即可。所有在虚拟机规范之中没有明确描述的实现细节,都不应成为虚拟机设计者发挥创造性的牵
绊,设计者可以完全自主决定所有规范中不曾描述的虚拟机内部细节,例如:运行时数据区的内存
如何布局、选用哪种垃圾收集的算法、是否要对虚拟机字节码指令进行一些内部优化操作(如使用
即时编译器把字节码编译为机器码)
。
以在 Unicode 的网站(http://www.unicode.org)中查找到。
的二进制格式来表示,并且经常(但并非绝对)以文件的形式存储,因此这种格式被称为 Class
文件格式。Class 文件格式中精确地定义了类与接口的表示形式,包括在平台相关的目标文件格
式中一些细节上的惯例①,例如字节序(Byte Ordering)等。
①
译者注:读者请勿误认为此处“平台相关的目标文件格式”是指在特定平台编译出的 Class 文件无法在其
他平台中使用。相反,正是因为强制、明确地定义了本来会跟平台相关的细节,所以才达到了平台无关的效果。例
如在 SPARC 平台上数字以 Big-Endian(高位的 Byte 放在内存中的低地址处)形式存储,在 x86 平台上数字则
是以 Little-Endian(低位的 Byte 放在内存中的高地址处)形式存储的,如果不强制统一字节序的话,同一个
Class 文件的二进制形式放在不同平台上就可能被不同的方式解读。
第 21 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.2 数据类型
型的数值可用于变量赋值、参数传递、方法返回和运算操作。
Java 虚拟机希望尽可能多的类型检查能在程序运行之前完成,换句话说,编译器应当在编译
期间尽最大努力完成可能的类型检查,使得虚拟机在运行期间无需进行这些操作。原始类型的值不
需要通过特殊标记或别的额外识别手段来在运行期确定它们的实际数据类型,也无需刻意将它们与
引用类型的值区分开来,虚拟机的字节码指令本身就可以确定它的指令操作数的类型是什么,所以
可以利用这种特性即可直接确定操作数的数值类型。举个例子,iadd、ladd、fadd 和 dadd 这
几条指令的操作含义都是将两个数值相加,并返个相加的结果,但是每一条指令都有自己的专属操
作数类型,此处按顺序分别为:int、long、float 和 double。关于虚拟机字节码指令的介绍,
读者可以参见本章的“§2.11 指令集简介”部分。
Java 虚拟机是直接支持对象的,这里的对象可以是指动态分配的某个类的实例,也可以指某
值读者可以想象成类似于一个指向对象的指针。每一个对象都可能存在多个指向它的引用,对象的
2.3 原始类型与值
其中:
①
译者注:这里的 reference 类型与 int、long、double 等类型是同一个层次的概念,reference 是前
文提到过的引用类型(Reference Types)的一种,而 int、long、double 等则是前面提到的原始类型
(Primitive Types)的一种。前者是具体的数据类型,后者是某种数据类型的统称,原文使用不同的英文字体
标识,译者根据通常使用习惯,在译文中把具体类型使用小写英文表示,而类型统称则翻译为中文。
第 22 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
整数类型包括:
的 null 值('\u0000')。
浮点类型包括:
float 类型:值为单精度浮点数集合②中的元素,或者(如果虚拟机支持的话)是单精度
扩展指数(Float-Extended-Exponent)集合中的元素。默认值为正数零。
double 类型:取值范围是双精度浮点数集合中的元素,或者(如果虚拟机支持的话)是
双精度扩展指数(Double-Extended-Exponent)集合中的元素。默认值为正数零。
布尔类型:
returnAddress 类型:
returnAddress 类型:表示一条字节码指令的操作码(Opcode)。在所有的虚拟机支
起来的。
2.3.1 整型类型与整型值
Java 虚拟机中的整型类型的取值范围如下:
32767。
①
译者注:基本多文本平面相关解释可参见:“http://zh.wikipedia.org/zh/基本多文種平面”
②
译者注:单精度浮点数集合、双精度浮点数集合、单精度扩展指数集合和双精度扩展指数集合将会在稍后的
§2.3.2 中详细介绍。
第 23 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2147483648 和 2147483647。
2.3.2 浮点类型、取值集合及浮点值
NaN 值用于表示某些无效的运算操作,例如除数为零等情况。
所有 Java 虚拟机的实现都必须支持两种标准的浮点数值集合:单精度浮点数集合和双精度浮
点数集合。另外,Java 虚拟机实现可以自由选择是否要支持单精度扩展指数集合和双精度扩展指
数集合,也可以选择支持其中的一种或全部。这些扩展指数集合可能在某些特定情况下代替标准浮
浮点数使用这种规则得到的表示形式可能不是唯一的,例如在指定的数值集合内,可以存在一个数
(Normalized Representation),不满足这个条件的其他表示形式就称为非标准表示
不存在任何标准表示,那就称这个数字为非标准值(Denormalized Value)。
在两个必须支持的浮点数值集合和两个可选的浮点数值集合内,对参数 N 和 K(也包括衍生参
第 24 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
表 2-1 浮点数值集合的参数
N 24 24 53 53
K 8 ≥11 11 ≥15
如果虚拟机实现支持了(无论是支持一种还是支持全部)扩展指数集合,那每一种被支持的扩
上述四种数值集合都不仅包含可数的非零值,还包括五个特殊的数值:正数零、负数零、正无
穷大、负无穷大和 NaN。
中的元素都一定是单精度扩展指数集合、双精度浮点数集合和双精度扩展指数集合中的元素。与此
类似,每一个双精度浮点数集合中的元素,都一定是双精度扩展指数集合的元素。换句话说,每一
种扩展指数集合都有比相应的标准浮点数集合更大的指数取值范围,但是不会有更高的精度。
(§4.4.4,§4.4.5)以外,本规范并不特别要求表示浮点数值表示形式。
上面提到的单精度浮点数集合、单精度扩展指数集合、双精度浮点数集合和双精度扩展指数集
合都并不是具体的数据类型。虚拟机实现使用一个单精度浮点数集合的元素来表示一个 float 类
第 25 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
型的数值在所有场景中都是可行的,但是在某些特定的上下文环境中,也允许虚拟机实现使用单精
度扩展指数集合的元素来代替。类似的,虚拟机实现使用一个双精度浮点数集合的元素来表示一个
double 类型的数值在所有场景中都是可行的,但是在某些特定的上下文环境中,也允许虚拟机实
现使用双精度扩展指数集合的元素来代替。
除了 NaN 以外,浮点数集合中的所有元素都是有序的。如果把它们从小到大按顺序排列好,
那顺序将会是:负无穷,可数负数、正负零、可数正数、正无穷。
returnAddress 类型的值指向一条虚拟机指令的操作码。与前面介绍的那些数值类的原始类型
returnAddress 类型的值。
2.3.4 boolean 类型
①
译者注:这几条指令以前主要被使用来实现 finally 语句块,后来改为冗余 finally 块代码的方式来实
现,甚至到了 JDK7 时,虚拟机已不允许 Class 文件内出现这几条指令。那相应地,returnAddress 类型就处
于名存实亡的状态。
第 26 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.4 引用类型与值
接口类型(Interface Types)。这些引用类型的值分别由类实例、数组实例和实现了某个接口
的类实例或数组实例动态创建。
其中,数组类型还包含一个单一维度(即长度不由其类型决定)的组件类型(Component
Type),一个数组的组件类型也可以是数组。但从任意一个数组开始,如果发现其组件类型也是数
组类型的话,继续重复取这个数组的组件类型,这样操作不断执行,最终一定可以遇到组件类型不
是数组的情况,这时就把这种类型成为数组类型的元素类型(Element Type)。数组的元素类型
必须是原始类型、类类型或者接口类型之中的一种。
在引用类型的值中还有一个特殊的值:null,当一个引用不指向任何对象的时候,它的值就
体上下文时它可转型为任意的引用类型。引用类型的默认值就是 null。
2.5 运行时数据区
Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机
启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程对应的数据区
域会随着线程开始和结束而创建和销毁。
①
在 Oracle 公司的虚拟机实现里,Java 语言里面的 boolean 数组将会被编码成 Java 虚拟机的 byte 数
组,每个 boolean 元素占 8 位长度。
第 27 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.5.1 PC 寄存器
只会执行一个方法的代码,这个正在被线程执行的方法称为该线程的当前方法(Current
言(例如 C 语言)中的栈非常类似,就是用于存储局部变量与一些过程结果的地方。另外,它在
方法调用和返回中也扮演了很重要的角色。因为除了栈帧的出栈和入栈之外,Java 虚拟机栈不会
再受其他因素的影响,所以栈帧可以在堆中分配②,Java 虚拟机栈所使用的内存不需要保证是连
续的。
建的时候独立地选定。Java 虚拟机实现应当提供给程序员或者最终用户调节虚拟机栈初始容量的
Java 虚拟机栈可能发生如下异常情况:
个 StackOverflowError 异常。
如果 Java 虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的
①
在 Java 虚拟机规范第一版之中,Java 虚拟机栈也被称为“Java 栈”。
②
译者注:请读者注意避免混淆 Stack、Heap 和 Java (VM)Stack、Java Heap 的概念,Java 虚拟机
的实现本身是由其他语言编写的应用程序,在 Java 语言程序的角度上看分配在 Java Stack 中的数据,而在实
现虚拟机的程序角度上看则可以是分配在 Heap 之中。
第 28 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚
2.5.3 Java 堆
在 Java 虚拟机中,堆(Heap)是可供各条线程共享的运行时内存区域,也是供所有类实例
和数组对象分配内存的区域。
用什么具体的技术去实现自动内存管理系统。虚拟机实现者可以根据系统的实际需要来选择自动内
存管理技术。Java 堆的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,并在不需
要过多空间时自动收缩。Java 堆所使用的内存不需要保证是连续的。
Java 堆可能发生如下异常情况:
OutOfMemoryError 异常。
2.5.4 方法区
的正文段(Text Segment)的作用非常类似,它存储了每一个类的结构信息,例如运行时常量
括一些在类、实例、接口初始化时用到的特殊方法(§2.9)。
方法区在虚拟机启动的时候被创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现
第 29 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
置和编译代码的管理策略。方法区的容量可以是固定大小的,也可以随着程序执行的需求动态扩展,
并在不需要过多空间时自动收缩。方法区在实际内存空间中可以是不连续的。
Java 虚拟机实现应当提供给程序员或者最终用户调节方法区初始容量的手段,对于可以动态
扩展和收缩方法区来说,则应当提供调节其最大、最小容量的手段。
方法区可能发生如下异常情况:
OutOfMemoryError 异常。
2.5.5 运行时常量池
§4.4)的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行
期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表(Symbol
Table)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。
虚拟机后,对应的运行时常量池就被创建出来。
在创建类和接口的运行时常量池时,可能会发生如下异常情况:
当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最
关于构造运行时常量池的详细信息,可以参考“第 5 章 加载、链接和初始化”的内容。
2.5.6 本地方法栈
地方法栈,如果支持本地方法栈,那这个栈一般会在线程创建的时候按线程分配。
第 30 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
Java 虚拟机规范允许本地方法栈被实现成固定大小的或者是根据计算动态扩展和收缩的。如
果采用固定大小的本地方法栈,那每一条线程的本地方法栈容量应当在栈创建的时候独立地选定。
一般情况下,Java 虚拟机实现应当提供给程序员或者最终用户调节虚拟机栈初始容量的手段,对
于长度可动态变化的本地方法栈来说,则应当提供调节其最大、最小容量的手段。
本地方法栈可能发生如下异常情况:
如果线程请求分配的栈容量超过本地方法栈允许的最大容量时,Java 虚拟机将会抛出一个
StackOverflowError 异常。
如果本地方法栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存
去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的本地方法栈,那 Java 虚拟
2.6 栈帧
栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接
栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出
之中,每一个栈帧都有自己的局部变量表(Local Variables,§2.6.1)、操作数栈(Operand
Stack,§2.6.2)和指向当前方法所属的类的运行时常量池(§2.5.5)的引用。
内存。
在一条线程之中,只有目前正在执行的那个方法的栈帧是活动的。这个栈帧就被称为是当前栈
这个方法的类就称作当前类(Current Class)。对局部变量表和操作数栈的各种操作,通常都
指的是对当前栈帧的对局部变量表和操作数栈进行的操作。
如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧
了。当一个新的方法被调用,一个新的栈帧也会随之而创建,并且随着程序控制权移交到新的方法
而成为新的当前栈帧。当方法返回的之际,当前栈帧会传回此方法的执行结果给前一个栈帧,在方
第 31 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
法返回之后,当前栈帧就随之被丢弃,前一个栈帧就重新成为当前栈帧了。
请读者特别注意,栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈
帧。
2.6.1 局部变量表
每个栈帧(§2.6)内部都包含一组称为局部变量表(Local Variables)的变量列表。栈
帧中局部变量表的长度由编译期决定,并且存储于类和接口的二进制表示之中,既通过方法的
Code 属性(§4.7.3)保存及提供给栈帧使用。
一个局部变量可以保存一个类型为 boolean、byte、char、short、float、reference
局部变量使用索引来进行定位访问,第一个局部变量的索引值为零,局部变量的索引值是从零
至小于局部变量表最大容量的所有整数。
是无法直接读取的,但是可能会被写入,不过如果进行了这种操作,就将会导致局部变量 n 的内
容失效掉。
类型数据采用 64 位对其的方式存放在连续的局部变量中。虚拟机实现者可以自由地选择适当的方
Java 虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用的时候,它的
参数将会传递至从 0 开始的连续的局部变量表位置上。特别地,当一个实例方法被调用的时候,
关键字)。后续的其他参数将会传递至从 1 开始的连续的局部变量表位置上。
第 32 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.6.2 操作数栈
每一个栈帧(§2.6)内部都包含一个称为操作数栈(Operand Stack)的后进先出
(Last-In-First-Out,LIFO)栈。栈帧中操作数栈的长度由编译期决定,并且存储于类和接
在上下文明确,不会产生误解的前提下,我们经常把“当前栈帧的操作数栈”直接简称为“操
作数栈”。
操作数栈所属的栈帧在刚刚被创建的时候,操作数栈是空的。Java 虚拟机提供一些字节码指
令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于
从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备
调用方法的参数以及接收方法返回结果。
从操作栈中出栈,相加求和,然后将求和结果重新入栈。在操作数栈中,一项运算常由多个子运算
(Subcomputations)嵌套进行,一个子运算过程的结果可以被其他外围运算所使用。
在操作数栈中的数据必须被正确地操作,这里正确操作是指对操作数栈的操作必须与操作数栈
中的数据当作裸类型(Raw Type)数据来操作,这些指令不可以用来修改数据,也不可以拆散那
障。
两个单位的栈深度,其他数据类型则会占用一个单位深度。
第 33 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.6.3 动态链接
每一个栈帧(§2.6)内部都包含一个指向运行时常量池(§2.5.5)的引用来支持当前方法
或者访问其成员变量是通过符号引用(Symbolic Reference)来表示的,动态链接的作用就是
将这些符号引用所表示的方法转换为实际方法的直接引用。类加载的过程中将要解析掉尚未被解析
的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移
量。
由于动态链接的存在,通过晚期绑定(Late Binding)使用的其他类的方法和变量在发生
变化时,将不会对调用它们的方法构成影响。
2.6.4 方法正常调用完成
方法正常调用完成是指在方法的执行过程中,没有任何异常(§2.10)被抛出——包括直接
用正常完成的话,它很可能会返回一个值给调用它的方法,方法正常完成发生在一个方法执行过程
中遇到了方法返回的字节码指令(§2.11.8)的时候,使用哪种返回指令取决于方法返回值的数
据类型(如果有返回值的话)。
在这种场景下,当前栈帧(§2.6)承担着回复调用者状态的责任,其状态包括调用者的局部
变量表、操作数栈和被正确增加过来表示执行了该方法调用指令的程序计数器等。使得调用者的代
码能在被调用的方法返回并且返回值被推入调用者栈帧的操作数栈后继续正常地执行。
2.6.5 方法异常调用完成
(§2.10),并且虚拟机抛出的异常在该方法中没有办法处理,或者在执行过程中遇到了 athrow
字节码指令显式地抛出异常,并且在该方法内部没有把异常捕获住。如果方法异常调用完成,那一
定不会有方法返回值返回给它的调用者。
第 34 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.7 对象的表示
Java 虚拟机规范不强制规定对象的内部结构应当如何表示①。
2.8 浮点算法
溢出(Overflow)、下限溢出(Underflow)和非精确(Inexact)时,不会抛出
Comparisons)。
①
在一些 Oracle 的 Java 虚拟机实现中,指向对象实例的引用是一个指向句柄的指针,这个句柄包含两部分
信心,一部分是指向这个对象所包括的方法表以及指向这个对象所属类相关的信息;另一部分是指向在堆中分配的
对象实例数据。
(译者注:这条注释在 10 多年前出版的 Java 虚拟机规范第二版中就已经存在,第三版中仅仅是将
Sun 修改为 Oracle 而已,所表达的实际信息已比较陈旧。在 HotSpot 虚拟机中,指向对象的引用并不通过句柄,
而是直接指向堆中对象的实例数据,因此 HotSpot 虚拟机并不包括在上面所描述的“一些 Oracle 的 Java 虚拟
机实现”范围之内)
第 35 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
算的舍入模式①。
精度扩展指数格式的表示方位会有重叠。虚拟机实现可以选择是否支持的单精度扩展指数
中的扩展格式规定了扩展精度与扩展指数的范围。
2.8.2 浮点模式
每一个方法都有一项属性称为浮点模式(Floating-Point Mode),取值有两种,要么是
本没有效果。
我们说一个操作数栈具有某种浮点模式时,所指的就是包含操作数栈的栈帧所对应的方法具备
条指令的方法具备的浮点模式。
点数集合的范围。同样的,如果虚拟机实现支持双精度指数扩展集合(§2.3.2),那在非
型的值将可能会超过双精度浮点数集合的范围。
在其他的上下文中,无论是操作数栈或者别的地方都不再特别去关注浮点模式,float 和
①
译者注:在 IEEE 754 中定义了 4 种舍入模式,除了上面提到的向最接近数舍入和向零舍入以外,还有向
正无穷舍入和向负无穷舍入两种模式。向最接近数舍入模式既我们平常所说的“四舍五入”法,而向零舍入既平常
所说的“去尾”法舍入。
第 36 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
double 两种浮点值都分别限于单精度和双精度浮点数集合之中。类和实例的字段、数组元素、局
部变量和方法参数的取值范围都限于标准的数值集合之中。
2.8.3 数值集合转换
集合之间的映射关系是允许和必要的,这种映射操作就称为数值集合转换。数值集合转换并非数据
类型转换,而是在同一种数据类型之中不同数值集合的映射操作。
在数值集合转换发生的位置,虚拟机实现允许对数值执行下面操作之一:
精度浮点数集合中数值最接近的元素。
双精度浮点数集合中数值最接近的元素。
此外,在数值集合转换发生的位置,下面操作是必须的:
传递或者存储进局部变量、字段或者数组元素之中。如果这个数值不是单精度浮点数集合
中的元素,需要将其映射到单精度浮点数集合中数值最接近的元素。
传递或者存储进局部变量、字段或者数组元素之中。如果这个数值不是双精度浮点数集合
中的元素,需要将其映射到双精度浮点数集合中数值最接近的元素。
数值到局部变量、字段或者数组元素之中都可能会导致上述的数值集合转换发生。
并非所有扩展指数集合中的数值都可以精确映射到标准浮点数值集合的元素之中。如果进行映
射的数值过大(扩展指数集合的指数可能比标准数值集合的允许最大值要大),无法在标准数值集
合之中精确表示的话,这个数字将会被转化称对应类型的(正或负)无穷大。如果进行映射的数值
过大(扩展指数集合的指数可能比标准数值集合的允许最小值要小),无法在标准数值集合之中精
第 37 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
确表示的话,这个数字将会被转化成最接近的可以表示非正规值(Denormalized Value,§
2.3.2)或者相同正负符号零。
2.9 初始化方法的特殊命名
JLS3,§8.8)是以一个名为<init>的特殊实例初始化方法的形式出现的,<init>这个方法名
只有在实例正在构造的时候,实例初始化方法才可以被调用访问(JLS3,§6.6)。
一个类或者接口最多可以包含不超过一个类或接口的初始化方法,类或者接口就是通过这个方
法完成初始化的(§5.5)。这个方法是一个不包含参数的静态方法,名为<clinit>①。这个名字
法,只有在类的初始化阶段中会被虚拟机自身调用。
2.10 异常
序控制权的一种即时的、非局部(Nonlocal)的转换——从异常抛出的地方转换至处理异常的地
方。
绝大多数的异常的产生都是由于当前线程执行的某个操作所导致的,这种可以称为是同步的异
常。与之相对的,异步异常是指在程序的其他任意地方进行的动作而导致的异常。Java 虚拟机中
异常的出现总是由下面三种原因之一导致的:
1. 虚拟机同步检测到程序发生了非正常的执行情况,这时异常将会紧接着在发生非正常执行
情况的字节码指令之后抛出。例如:
①
在 Class 文件中把其他方法命名为<clinit>是没有意义的,这些方法并不是类或接口的初始化方法,它
们既不能被字节码指令调用,也不会被虚拟机自己调用。
第 38 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
元素。
类在加载或者链接时出现错误。
使用某些资源的时候产生资源限制,例如使用了太多的内存。
2. athrow 字节码指令被执行。
3. 由于以下原因,导致了异步异常的出现:
Java 虚拟机实现的内部程序错误。
这时候其他线程中出现的异常就是异步异常,因为这些异常可能出现在程序执行过程的任
何位置。虚拟机的内部异常也被认为是一种异步异常(§6.3)
《Java 虚拟机规范》允许在异步异常被抛出时额外执行一小段有限的代码,允许代码优化器
程序控制权发生转移的那一刻,所有在异常抛出的位置之前的字节码指令所产生的影响②都应当是
可以被观察到的,而在异常抛出的位置之后的字节码指令,则应当是没有被执行过的。如果虚拟机
执行的代码是被优化后的代码③,有一些在异常出现位置之后的代码可能已经被执行了,那这些优
化过的代码必须保证被它们提前执行所产生的影响对用户程序来说都是不可见的。
异常处理器描述了其在方法代码中的有效作用范围(通过字节码偏移量范围来描述)、能处理的异
常类型以及处理异常的代码所在的位置。要判断某个异常处理器是否可以处理某个具体的异常,需
要同时检查异常出现的位置是否在异常处理的有效作用范围内并且出现的异常是否异常处理器声
①
对于简单的 Java 虚拟机实现,可以把异步异常简单地放在程序控制权转移指令上处理。因为程序终归是有
限的,总会遇到控制权转移的指令,所以异步异常抛出的延迟时间也是有限的。如果能保证在控制权转移指令之间
的代码没有异步异常抛出,那代码生成器就可以有相当的灵活性进行指令重排序优化来获取更好的性能。相关的资
料推荐进一步阅读论文:《Polling Efficiently on Stock Hardware》,Marc Feeley,Proc.1993,
《Conference on Functional Programming and Computer Architecture》,Copenhagen,Den-mark,
第 179–187 页。
②
译者注:这里的“影响”包括了异常出现之前的字节码指令执行后对局部变量表、操作数栈、其他运行时数
据区域以及虚拟机外部资源产生的影响。
③
译者注:这里的“优化后的代码”主要是指进行了指令重排序优化的代码。
第 39 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
明可以处理的异常类型或其子类型两个条件。当有异常被抛出时,Java 虚拟机搜索当前方法的包
含的各个异常处理器,如果能找到可以处理该异常的异常处理器,则将代码控制权转向到异常处理
器中描述的处理异常的分支之中。
异常处理器表描述异常处理器的先后顺序,从前至后进行搜索。
需要注意,Java 虚拟机本身不会对方法的对异常处理器表做排序或者其他方式的强制处理,
所以 Java 语言中对异常处理的语义,实际上是通过编译器适当安排异常处理器在表中的顺序来协
何种途径产生的,Java 虚拟机执行时都能有一致的行为表现。
2.11 字节码指令集简介
Java 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(Opcode)以及
跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成。虚拟机中许多指令并不
包含操作数,只有一个操作码。
do {
自动计算 PC 寄存器以及从 PC 寄存器的位置取出操作码;
if (存在操作数) 取出操作数;
执行操作码所定义的操作
} while (处理下一次循环);
操作数的数量以及长度取决于操作码,如果一个操作数的长度超过了一个字节,那它将会以
字节码指令流应当都是单字节对齐的,只有“tableswitch”和“lookupswitch”两条指
令例外,由于它们的操作数比较特殊,都是以 4 字节为界划分开的,所以这两条指令那个也需要
预留出相应的空位来实现对齐。
限制 Java 虚拟机操作码的长度为一个字节,并且放弃了编译后代码的参数长度对齐,是为了
第 40 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
为代价。由于每个操作码只能有一个字节长度,所以直接限制了整个指令集的数量①,又由于没有
假设数据是对齐好的,这就意味着虚拟机处理那些超过一个字节的数据的时候,不得不在运行时从
字节中重建出具体数据的结构,这在某种程度上会损失一些性能。
在 Java 虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。举个例子,
float 类型的数据。这两条指令的操作可能会是由同一段代码来实现的,但它们必须拥有各自独
立的操作符。
对于大部分为与数据类型相关的字节码指令,他们的操作码助记符中都有特殊的字符来表明专
门为哪种数据类型服务:
i 代表对 int 类型的数据操作,l 代表 long,s 代表 short,b 代表 byte,
据类型无关的。
由于 Java 虚拟机的操作码长度只有一个字节,所以包含了数据类型的操作码对指令集的设计
话,那恐怕就会超出一个字节所能表示的数量范围了。因此,Java 虚拟机的指令集对于特定的操
作只提供了有限的类型相关指令去支持它,换句话说,指令集将会故意被设计成非完全独立的(Not
Orthogonal,即并非每种数据类型和每一种操作都有对应的指令)。有一些单独的指令可以在必
要的时候用来将一些不支持的类型转换为可被支持的类型。
据类型两列共同确定的格为空,则说明虚拟机不支持对这种数据类型执行这项操作。例如 load 指
①
译者注:字节码无法超过 256 条的限制就来源于此。
第 41 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
算类型(Computational Type)。
e ce
h h
t t t
e e e
Tinc iinc
d d d d d d
re re re re re re
Tcmp lcmp
OP OP OP
rn rn rn
实际类型 运算类型 分类
第 43 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
2.11.2 加载和存储指令
加载和存储指令用于将数据从栈帧(§2.6)的局部变量表(§2.6.1)和操作数栈之间来回
传输(§2.6.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.5)的指令也同样会与操作数栈传输数据。
上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如 iload_<n>),这些指令助
形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作
数都是在指令中隐含的。除此之外,他们的语义与原生的通用指令完全一致(例如 iload_0 的语
这种指令表示方法,在整个《Java 虚拟机规范》之中都是通用的。
第 44 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
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
Java 虚拟机没有明确规定整型数据溢出的情况,但是规定了在处理整型数据时,只有除法指
Numbers,§2.3.2)和逐级下溢(Gradual Underflow)。这些特征将会使得某些数值算法处
理起来变得更容易一些。
Java 虚拟机要求在进行浮点数运算时,所有的运算结果都必须舍入到适当的进度,非精确的
第 45 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
结果必须舍入为可被表示的最接近的精确值,如果有两种可表示的形式与该值一样接近,那将优先
舍入模式(§2.8.1)。
这种模式的舍入结果会导致数字被截断,所有小数部分的有效字节都会被丢弃掉。向零舍入模式将
在目标数值类型中选择一个最接近,但是不大于原值的数字来作为最精确的舍入结果。
值作为操作数的算术操作,结果都会返回 NaN。
在对 long 类型数值进行比较时,虚拟机采用带符号的比较方式,而对浮点数值进行比较时
(Nonsignaling Comparisons)方式。
2.11.4 类型转换指令
题(§2.11.1)。
Java 虚拟机直接支持(译者注:“直接支持”意味着转换时无需显式的转换指令)以下数值
f2i、f2l、d2i、d2l 和 d2f。窄化类型转换可能会导致转换结果产生不同的正负号、不同的数
量级,转换过程很可能会导致数值丢失精度。
号(译者注:在高位字节符号位被丢弃了)。
下转换规则:
取整,获得整数值 v,这时候可能有两种情况:
类型数值 v
类型数值 v
否则:
如果转换结果 v 的值太小(包括足够小的负数以及负无穷大的情况),无法使用 T 类
如果转换结果 v 的值太大(包括足够大的正数以及正无穷大的情况),无法使用 T 类
换永远不可能导致虚拟机抛出运行时异常(此处的异常是指《Java 虚拟机规范》中定义的异常,
2.11.5 对象创建与操作
码指令:
创建类实例的指令:new
第 47 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
创建数组的指令:newarray,anewarray,multianewarray
实例变量)的指令:getfield、putfield、getstatic、putstatic
把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、
faload、daload、aaload
将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、
iastore、fastore、dastore、aastore
取数组长度的指令:arraylength
检查类实例类型的指令:instanceof、checkcast
2.11.6 操作数栈管理指令
Java 虚拟机提供了一些用于直接操作操作数栈的指令,包括:pop、pop2、dup、dup2、
dup_x1、dup2_x1、dup_x2、dup2_x2 和 swap。
2.11.7 控制转移指令
条指令继续执行程序。控制转移指令包括有:
条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、
if_icmpne、if_icmplt, if_icmpgt、if_icmple、if_icmpge、if_acmpeq 和
if_acmpne。
复合条件分支:tableswitch、lookupswitch
无条件分支:goto、goto_w、jsr、jsr_w、ret
第 48 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
会先执行相应类型的比较运算指令(§2.11.3),运算指令会返回一个整形值到操作数栈中,随
类型的条件分支指令。
所有 int 类型的条件分支转移指令进行的都是有符号的比较操作。
2.11.8 方法调用和返回指令
以下四条指令用于方法调用:
invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分
invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的
对象,找出适合的方法进行调用。
invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(§
2.9)、私有方法和父类方法。
2.11.9 抛出异常
他 Java 虚拟机指令检测到异常状况时由虚拟机自动抛出。
2.11.10 同步
Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使
第 49 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
用管程(Monitor)来支持的。
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作(§
2.11.8)之中。虚拟机可以从方法常量池中的方法表结构(method_info Structure,§4.6)
中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会
然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期
间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛
出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法
之外时自动释放。
中关于同步的描述)。
结构化锁定(Structured Locking)是指在方法调用期间每一个管程退出都与前面的管程
程,M 代表一个管程的话:
放管程 M 的次数相等。
多的情况。
请注意,在同步方法调用时自动持有和释放管程的过程也被认为是在方法调用期间发生。
2.12 类库
没有 Java 虚拟机的支持的话是根本无法实现的。
类和接口的加载和创建,最显而易见的例子就是 java.lang.ClassLoader 类
类和接口的链接和初始化,上一点的例子也适用于这点
他类
多线程,譬如 java.lang.Thread 类
上面列举的几点意在简单说明一下而不是详细介绍这些类库,介绍这些类库和功能的详细信息
信息。
2.13 公有设计,私有实现
跟随的计划蓝图。
理解公有设计与私有实现之间的分界线是非常有必要的,Java 虚拟机实现必须能够读取
变地逐字实现其中要求的内容当然是一种可行的途径,但实现者在本规范约束下对具体实现做出修
且包含在其中的语义能得到保持,那实现者就可以选择任何方式去实现这些语义,虚拟机后台如何
处理 Class 文件完全是实现者自己的事情,只要它在外部接口上看起来与规范描述的一致即可①。
以下两种:
①
这里多少存在一些例外:譬如调试器(Debuggers)、性能监视器(Profilers)和即时代码生成器
(Just-In-Time Code Generator)等都可能需要访问一些通常被认为是“虚拟机后台”的元素。Oracle
与其他 Java 虚拟机实现者以及工具提供商一起开发这类 Java 虚拟机工具的通用接口,令这些接口可以在整个行
业中通用。
第 51 页 / 共 387 页
Java 虚拟机规范 — 第 2 章 Java 虚拟机结构
精确定义的虚拟机和目标文件格式不应当对虚拟机实现者的创造性产生太多的限制,Java 虚
拟机是被设计成可以允许有众多不同的实现,并且各种实现可以在保持兼容性的同时提供不同的新
的、有趣的解决方案。
第 52 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
第 3 章 为 JAVA 虚拟机编译
译为 Java 虚拟机指令集的编译器。
3.1 示例的格式说明
译方法的例子。
如果读者阅读过汇编代码,应该很熟悉示例中的格式。所有指令的格式如下:
方法起始处的字节偏移量。<opcode>为指令的操作码的助记符号,<operandN>是指令的操作数,
一条指令可以有 0 至多个操作数。<comment>为行尾的语法注释,譬如:
第 53 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
内容中)按照更容易被人所阅读的方式来显示。
在每一行中,在表示运行时常量池索引的操作数前,会井号(’#’)开头,在指令后的注释中,
会带有这个操作数的描述,譬如:
本章节主要目的是描述虚拟机的编译过程,我们将忽略一些诸如操作数容量等的细节问题。
3.2 常量、局部变量的使用和控制结构①
们就可以感受到许多这类特性,示例如下:
void spin() {
int i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
编译后代码如下:
①
译者注:控制结构(Control Constructs)是指控制程序执行路径的语句体。譬如 for、while 等循环、
条件分支等。
第 54 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
操作数,或将结果压入操作数栈中。每调用一个方法,都会创建一个新的栈帧,并创建对应方法所
需的操作数栈和局部变量表(参见§2.6 “栈帧”)。每条线程在运行时的任意时刻,都会包含若
干个由不同方法嵌套调用而产生的栈帧,当然也包括了若干个栈帧内部的操作数栈,但是只有当前
栈帧中的操作数栈才是活动的。
Java 虚拟机指令集合使用不同的字节码来区分不同的操作数类型,用于操作各种类型的变
它的立即操作数(Immediate Operand)①压入到操作数栈中。
不需要专门为入栈操作保存一个立即操作数的值,这样也避免了操作数的读取和解析(Decode)
次循环时消耗更多的时间用于获取和解析这个操作数。因此使用隐式操作数可让编译后的代码更简
洁,更高效。
译代码中,在局部变量表和操作数栈之间传输值的指令很常见的,在指令集里,这类操作也有特殊
指令都隐式指明了是对于第一个局部变量进行操作。istore_1 指令作用是从操作数栈中弹出一
作数栈。
①
译者注:在指令流中直接跟随在指令后面,而不是在操作数栈中的操作数称为立即操作数(也有直译为立即
数或直接操作数)。
②
译者注:读者需要注意到,局部变量的编号从 0 开始,。
第 55 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
编译器的开发者应尽可能多的重用局部变量,这样会使得代码更高效,更简洁,占用的内存(当前
栈帧的空间)更少。
spin()方法的循环部分由这些指令完成:
void dspin() {
double i;
for (i = 0.0; i < 100.0; i++) {
; // Loop body is empty
}
}
编译后代码如下:
第 56 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
候讨论)。
值的访问:
编译后代码如下:
对局部变量不能被分开来进行单个操作。
Java 虚拟机无法对全部操作都一视同仁地提供所有数据类型的支持。换句话说,并非每条指令都
指令集所支持的数据类型”)。
实现。
void sspin() {
short i;
for (i = 0; i < 100; i++) {
; // Loop body is empty
}
}
范围之内,因此 sspin()的编译后代码如下:
的支持。
3.3 算术运算
行自增操作)
,譬如下面的 align2grain()方法,它的作用是将 int 值对齐到 2 的指定幂次:
第 58 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
算术运算使用到的操作数都是从操作数栈中弹出的,运算结果被压回操作数栈中。在内部运算
时,中间运算(Arithmetic Subcomputations)的结果可以被当作操作数使用。譬如
~(grain-1)的值就是被这样使用的:
操作数从操作数栈中弹出,然后它们将被改变,最后再入栈到操作数栈之中。这里被改变是单个操
作为 iand 指令的操作数被使用。
整个方法的编译代码如下:
3.4 访问运行时常量池
很多数值常量,以及对象、字段和方法,都是通过当前类的运行时常量池进行访问。对象的访
第 59 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
使用 fconst_<f>和 dconst_<d>指令进行访问。
通过以上描述,编译的规则已经基本解释清楚了。下面这个例子将这些规则汇总了起来:
void useManyNumeric() {
int i = 100;
int j = 1000000;
long l1 = 1;
long l2 = 0xffffffff;
double d = 2.2;
...do some calculations...
}
编译后代码如下:
第 60 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
3.5 更多的控制结构示例
(if-then-else、do、while、break 以及 continue)也有特定的编译规则。本规范将在§
void whileInt() {
int i = 0;
while (i < 100) {
i++;
}
}
编译后代码如下:
环的最底部,这和§3.2 节中 spin()方法的条件判断位置一致。原本处于循环最底部、在迭代结
在期望循环体会被执行的场景之中(而不是用来当作 if 语句使用)。在接下来的循环迭代中,由
于循环体结束时跳转回顶部。
虚拟机对各种数据类型的控制结构采用了相似的方式编译,只是根据不同数据类型使用不同的
第 61 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
现,譬如:
void whileDouble() {
double i = 0.0;
while (i < 100.1) {
i++;
}
}
编译后代码如下:
所以如果有其中一个操作数为 NaN,则所有浮点型的比较指令都失败①。无论是比较操作是否会因
int lessThan100(double d) {
if (d < 100.0) {
return 1;
} else {
return -1;
}
}
编译后代码如下:
①
译者注:请注意,
“比较失败(Comparisons Fail)”的意思是比较指令返回“fail(对于 fcmpl 为-1,
而 fcmpg 为 1)”的结果到操作数栈,而不是抛出异常。在 Java 虚拟机指令集中,所有的算术比较指令都不会抛
出异常。
第 62 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
如果比较逻辑相反,dcmpl 指令可以达到相同的效果,譬如:
int greaterThan100(double d) {
if (d > 100.0) {
return 1;
} else {
return -1;
}
}
编译后代码如下:
第 63 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
3.6 接收参数
如果传递了 n 个参数给某个实例方法,则当前栈帧会按照约定的顺序接收这些参数,将它们
编译后代码如下:
关键字。如果 addTwo()是类方法,那么接收的参数和之前示例相比略有不同:
编译后代码如下:
两段代码唯一的区别是,后者方法保存参数到局部变量表时,是从编号为 0 的局部变量开始
而不是 1。
3.7 方法调用
对普通实例方法调用是在运行时根据对象类型进行分派的(相当于在 C++中所说的“虚方法”),
第 64 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
引的参数,运行时常量池在该索引处的项为某个方法的符号引用,这个符号引用可以提供方法所在
对象的类型的内部二进制名称、方法名称和方法描述符(§4.3.3)。下面这个例子中定义了一个
int add12and13() {
return addTwo(12, 13);
}
编译后代码如下:
方法调用过程的第一步是将当前实例的自身引用“this”压入到操作数栈中。传递给方法的
用者 add12and13()方法的代码可以立刻使用到的地方。
至调用者调用方法的下一条指令继续执行,并将调用者的栈帧重新设为当前栈帧。Java 虚拟机对
不同数据类型(包括声明为 void,没有返回值的方法)的返回值提供了不同的方法返回指令,各
种不同返回值类型的方法都使用这一组返回指令来返回。
保存于运行时常量池即可,这些运行时常量池项将会在执行时转换成调用方法的实际地址。Java
如果上个例子中,调用的实例方法 addTwo()变成类(static)方法的话,编译代码会有略
微变化,如下:
int add12and13() {
return addTwoStatic(12, 13);
}
类方法和实例方法的调用的编译代码很类似,两者的区别仅仅是实例方法需要调用者传递
有所区别(参见§3.6 “接收参数”
)。invokestatic 指令用于调用类方法。
class Near {
int it;
public int getItNear() {
return getIt();
}
private int getIt() {
return it;
}
}
第 66 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
第一个局部变量之中。
如果编译器要调用某个方法,必须先产生这个方法描述符,描述符中包含了方法实际参数和返
回类型。编译器在方法调用时不会处理参数的类型转换问题,只是简单地将参数的压入操作数栈,
且不改变其类型。通常,编译器会先把一个方法所在的对象的引用压入操作数栈,方法参数则按顺
描述符,这个描述符提供了方法参数和返回值的信息。作为方法解析(§5.4.3.3)时的一个特
法规则,并且在描述符中确定的类型信息将会被解析。
3.8 使用类实例
造函数将会以一个编译器提供的以<init>命名的方法出现。这个特殊的名字的方法也被称作实例
初始化方法(§2.9)。一个类可以有多个构造函数,对应地也就会有多个实例初始化方法。一旦
类实例被创建,那么这个实例包含的所有实例变量,除了在本身定义的以及父类中所定义的,都将
被赋予默认初始值,接着,新对象的实例初始化方法将会被调用。譬如下面示例:
Object create() {
return new Object();
}
编译后代码如下:
第 67 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
7 areturn
MyObj silly(MyObj o) {
if (o != null) {
return o;
} else {
return o;
}
}
编译后代码如下:
}
int getIt() {
return i;
}
编译后代码如下:
时常量池索引#4)都并非类实例中的地址偏移量。编译器会为这些字段生成符号引用,保存在运
行时常量池之中。这些运行时常量池项会在解析阶段转换为引用对象中真实的字段位置。
3.9 数组
在 Java 虚拟机中,数组也使用对象来表示。数组由专门的指令集来创建和操作。newarray
指令用于创建元素类型为数值类型的数组。譬如:
void createBuffer() {
int buffer[];
int bufsz = 100;
int value = 12;
buffer = new int[bufsz];
buffer[10] = value;
value = buffer[11];
}
编译后代码如下:
anewarray 指令用于创建元素为引用类型的一维数组。譬如:
void createThreadArray() {
Thread threads[];
int count = 10;
threads = new Thread[count];
threads[0] = new Thread();
}
编译后代码如下:
anewarray 指令也可以用于创建多维数组的第一维。不过我们也可以选择采用
multianewarray 指令一次性创建多维数组。譬如三维数组:
int[][][] create3DArray() {
int grid[][][];
grid = new int[10][5][];
第 70 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
return grid;
}
编译后代码如下:
multianewarray 指令的第一个操作数是运行时常量池索引,它表示将要被创建的数组的成
员类型。第二个操作数是需要创建的数组的实际维数。multianewarray 指令可以用于创建所有
3.10 编译 switch 语句
int chooseNear(int i) {
switch (i) {
case 0: return 0;
case 1: return 1;
case 2: return 2;
default: return -1;
}
}
编译后代码如下:
第 71 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
分支执行。譬如:
int chooseFar(int i) {
switch (i) {
case -100: return -1;
case 0: return 0;
case 100: return 1;
default: return -1;
}
}
了 lookupswitch 指令:
第 72 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
0 iload_1
1 lookupswitch 3:
−100: 36
0: 38
100: 40
default:42
36 iconst_m1
37 ireturn
38 iconst_0
39 ireturn
40 iconst_1
41 ireturn
42 iconst_m1
43 ireturn
二分搜索)将会比直接使用线性扫描搜索来得更有效率。在从索引表确定分支偏移量的过程中,
值进行一次范围检查。因此,在如果不需要考虑空间效率时,tableswitch 指令相比
lookupswitch 指令有更高的执行效率。
3.11 使用操作数栈
Java 虚拟机为方便使用操作数栈,提供了的大量的不区分操作数栈数据类型的指令。这些指
如:
编译后代码如下:
第 73 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
注意,Java 虚拟机不允许作用于操作数栈的指令修改或者拆分那些不可拆分操作数(如存放
3.12 抛出异常和处理异常
编译后代码如下:
try-catch 结构的编译也同样简单,譬如:
void catchOne() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
}
第 74 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
编译后代码如下:
仔细看可发现,try 语句块被编译后似乎没有生成任何指令,就像它没有出现过一样。
tryItOut()调用 catchOne()方法后就返回了。
个 catch 语句块的会使编译器在异常表中增加一个成员(即一个异常处理器,§2.10, §
第 75 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
void catchTwo() {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc(e);
} catch (TestExc2 e) {
handleExc(e);
}
}
连续排列,在异常表中也会有对应的连续排列的成员,它们的排列的顺序和源码中的 catch 语句
块出现的顺序一致。
不能被捕获)
,那么 Java 虚拟机将把这个异常抛出给 catchTwo()方法的调用者,catchTwo()
语句的结构很相似,譬如:
void nestedCatch() {
try {
try {
tryItOut();
} catch (TestExc1 e) {
handleExc1(e);
}
} catch (TestExc2 e) {
handleExc2(e);
}
}
编译后代码如下:
第 77 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
0 12 12 Class TestExc2
语句在代码中的顺序对异常处理表进行排序,以保证在代码任何位置抛出的任何异常,都会被最接
表中顺序在内部异常之后。
将由外层的异常处理器进行处理。
论有没有抛出异常),finally 语句块中的内容都会被执行,譬如:
void tryFinally() {
try {
tryItOut();
} finally {
①
译者注:很早之前(JDK 1.4.2 之前)的 Sun Javac 已经不再为 finally 语句生成 jsr 和 ret 指令了,
而是改为在每个分支之后冗余代码的形式来实现 finally 语句,所以在这节开头作者需要特别说明。在版本号为
51.0(JDK 7 的 Class 文件)的 Class 文件中,甚至还明确禁止了指令流中出现 jsr、jsr_w 指令。
第 78 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
wrapItUp();
}
}
编译后代码如下:
句)的下一句继续执行。
调用程序子片段的更多细节如下:在本例中,jsr 指令将其下一条指令的地址(即第 7 句的
地址)继续执行,至此 tryFinally()运行结束。
第 79 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
tryFinally()方法则在异常处理器表中寻找一个合适的异常处理器。这个处理器被找到后,会
转到异常处理器实现代码处(这个例子中为编译代码的第 8 句)继续执行。编译代码第 8 句的
语句块的程序子片段。如果正常返回(finally 语句块正常运行结束)
,位于编译代码第 12 句的
tryFinally()方法的调用者。
void tryCatchFinally() {
try {
tryItOut();
} catch (TestExc e) {
handleExc(e);
} finally {
wrapItUp();
}
}
编译后代码如下:
第 80 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
编译器会产生这条指令。
指令,至此 tryCatchFinally()方法运行结束。
handleExc()抛出异常,则这时候异常表中对应为第二个异常处理器就会生效。此时程序将跳转
至第 20 句,进入这个异常处理器的代码,将前面抛出的异常保存为第 1 个局部变量中,第 26 句
tryCatchFinally()方法的调用者。
3.14 同步
Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。
用和返回指令实现的)都是如此。
void onlyMe(Foo f) {
synchronized(f) {
第 81 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
doSomething();
}
}
编译后代码如下:
执行,编译器会自动产生一个异常处理器(§2.10),这个异常处理器声明可处理所有的异常,它
3.15 注解
于包注解的一些额外规则。
第 82 页 / 共 387 页
Java 虚拟机规范 — 第 3 章 JAVA 虚拟机编译器
如果编译器遇到一个被注解的、声明为在运行时可见的包,那编译器将会生成一个表示接口的
Class 文件,内部形式为(§4.2.1)“package-name.package-info”。这个接口有默认的
§9.2)中被明确定义。
RuntimeVisibleAnnotations(§4.7.16)和 RuntimeInvisibleAnnotations(§
4.7.17)属性。
第 83 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
第 4 章 Class 文件格式
或接口的定义信息,但是相对地,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过
类加载器直接生成)。本章中,我们只是通俗地将任意一个有效的类或接口所应当满足的格式称为
“Class 文件格式”,即使它不一定以磁盘文件的形式存在。
java.io.DataOutputStream 等类来实现。
行读取。
各项按照严格顺序连续存放的,它们之间没有任何填充或对齐作为各项间的分隔符号。
构。尽管我们采用类似 C 语言的数组语法来表示表中的项,但是读者应当清楚意识到,表是由可
变长数据组成的复合结构(表中每项的长度不固定),因此无法直接将字节偏移量来作为索引对表
进行访问。而我们描述一个数据结构为数组(Array)时,就意味着它含有零至多个长度固定的项
组成,这个时候则可以采用数组索引的方式来访问它②。
①
译者注:Big-Endian 顺序是指按高位字节在地址最低位,最低字节在地址最高位来存储数据,它是 SPARC、
PowerPC 等处理器的默认多字节存储顺序,而 x86 等处理器则是使用了相反的 Little-Endian 顺序来存储数据。
为了保证 Class 文件在不同硬件上具备同样的含义,因此在 Java 虚拟机规范中是有必要严格规定了数据存储顺
序的。
②
译者注:虽然原文中在此定义了“表”和“数组”的关系,但在后文中依然存在表和数组混用的情况。译文
中作了一些修正,把各个数据项结构不一致的数据集合用“表”那表示,譬如“constant_pool 表”、
“attributes
表”,而把数据项结构一致的数据集合用“数组”来表示,譬如“code[]数组“、“fields[]数组”。
第 84 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.1 ClassFile 结构
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
ClassFile 结构体中,各项的含义描述如下:
magic
数值固定为 0xCAFEBABE,不会改变。
minor_version、major_version
第 85 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
反之则不成立①。
constant_pool_count
constant_pool[]
中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池中的每一项都具备相
同的格式特征——第一个字节作为类型标记用于识别该项是哪种类型的常量,称为“tag
byte”。常量池的索引范围是 1 至 constant_pool_count−1。
access_flags
访问标志,access_flags 是一种掩码标志,用于表示某个类或者接口的访问权限及基
表 4.1 访问和修饰符标志
标记名 值 含义
①
Oracle 的 JDK 在 1.0.2 版本时,支持的 Class 格式版本号范围是 45.0 至 45.3;JDK 版本在 1.1.x
时,支持的 Class 格式版本号范围扩展至 45.0 至 45.65535;JDK 版本为 1. k 时(k ≥2)时,对应的 Class
文件格式版本号的范围是 45.0 至 44+k.0
②
译者注:虽然值为 0 的 constant_pool 索引是无效的,但其他用到常量池的数据结构可以使用索引 0 来
表示“不引用任何一个常量池项”的意思。
③
译者注:此处“特殊处理”是相对于 JDK 1.0.2 之前的 Class 文件而言,invokespecial 的语义和处
理方式在 JDK 1.0.2 时发生了变化,为避免二义性,在 JDK 1.0.2 之后编译出的 Class 文件,都带有 ACC_SUPER
标志用以区分。
第 86 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
带有 ACC_SYNTHETIC 标志的类,意味着它是由编译器自己产生的而不是由程序员
编写的源代码生成的。
带有 ACC_ENUM 标志的类,意味着它或它的父类被声明为枚举类型。
带有 ACC_INTERFACE 标志的类,意味着它是接口而不是类,反之是类而不是接口。
this_class
(§4.4.1)
,表示这个 Class 文件所定义的类或接口。
super_class
第 87 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
java.lang.Object 类,只有它是唯一没有父类的类。
interfaces_count
接口计数器,interfaces_count 的值表示当前类或接口的直接父接口数量。
interfaces[]
interfaces_count。在 interfaces[]数组中,成员所表示的接口顺序和对应的源
代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左
边的接口。
fields_count
该类或接口声明的类字段或者实例字段①。
fields[]
据项,用于表示当前类或接口中某个字段的完整描述。fields[]数组描述当前类或接口
声明的所有字段,但不包括从父类或父接口继承的部分。
methods_count
methods[]
①
译者注:类字段即被声明为 static 的字段,也称为类变量或者类属性,同样,实例字段是指未被声明为
static 的字段。由于《Java 虚拟机规范》中,
“Variable”和“Attribute”出现频率很高且在大多数场景中
具备其他含义,所以译文中统一把“Field”翻译为“字段”,即“类字段”、“实例字段”。
第 88 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
数据项,用于表示当前类或接口中某个方法的完整描述。如果某个 method_info 结构
类。method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、
实例初始化方法方法(§2.9)和类或接口初始化方法方法(§2.9)。methods[]数组
只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
attributes_count
attributes[]
InnerClasses(§4.7.6)、EnclosingMethod(§4.7.7)、Synthetic(§4.7.8)
、
、SourceFile(§4.7.10),SourceDebugExtension
Signature(§4.7.9)
(§4.7.11)、Deprecated(§4.7.15)、RuntimeVisibleAnnotations
(§4.7.16)、RuntimeInvisibleAnnotations(§4.7.17)以及
(§4.7.9)
、RuntimeVisibleAnnotations(§4.7.16)和
4.2 各种内部表示名称
第 89 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.2.1 类和接口的二进制名称
(§4.4.7)
结构来表示,
因此如果忽略其他一些约束限制的话,这个名称可能来自整个 Unitcode
字符空间的任意字符组成。类和接口的二进制名称还会被 CONSTANT_NameAndType_info(§
4.4.6)结构所引用,用于构成它们的描述符(§4.3),引用这些名称通过引用它们的
CONSTANT_Class_info(§4.4.1)结构来实现。
规定的语法二进制名格式有差别。在本规范规定的内部形式中,用来分隔各个标识符的符号不在是
ASCII 字符点号('.')
,而是被 ASCII 字符斜杠('/')所代替,每个标识符都是一个非全限定
名(Unqualified Names,§4.2.2)①。
CONSTANT_Utf8_info 结构来实现的。
4.2.2 非全限定名
方法名,字段名和局部变量名都被使用非全限定名(Unqualified Names)进行存储。非
既类似"\u2E"这种形式)
。
方法的非全限定名还有一些额外的限制,除了实例初始化方法“<init>”和类初始化方法
Unicode 表示形式)②。
①
译者注:解释一下全限定名和非全限定名,全限定名是在整个 JVM 中的绝对名称,譬如
“java.lang.Object”,而非全限定名是指当前环境(譬如当前类)中的相对名称,譬如“Object”。
②
请注意:虽然字段名和接口方法名可以使用<init>和<clinit>(译者注:这里是在 Class 文件角度上描
述,Class 文件格式中可以存在这 2 个方法名。在 Java 程序编码时还要遵循 JLS 的约束,即 Java 源码中不能
存在这 2 个方法名),但是没有任何调用指令可以调用到<clinit>,也仅有 invokespecial 可以调用<init>。
第 90 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.3 描述符和签名
字符空间中的任意字符。
签名(Signature)是用于描述字段、方法和类型定义中的泛型信息的字符串。
4.3.1 语法符号
描述符和签名都是用特定的语法符号(Grammar)来表示,这些语法是一组可表达如何根据
不同的类型去产生可恰当描述它们的字符序列的标识集合。在本规范中,语法的终止符号用定长的
黑体字表示。非终止符号用斜体字表示,非终止符的定义由被定义的非终止名后跟随一个冒号表示。
冒号右侧的一个或多个可交换的非终止符连续排列,每个非终止符占一行①。例如:
FieldType:
BaseType
ObjectType
ArrayType
一。
当一个有星号(*)跟随的非终止符出现在一个语法标识的右侧时,说明带有这个非终止符的
语法标识将产生 0 或多个不同值,这些值按照顺序且无间隔的排列在非终止符后面。当一个有加
号(+)跟随的非终止符出现在一个结构的右侧时,说明这个非终止符将产生一个或多个不同值,
这些值按照顺序且无间隔的排列在非终止符后面。例如:
MethodDescriptor:
( ParameterDescriptor* ) ReturnDescriptor
①
译者注:由于中文、英文之间排版差异,译文中某些地方没有完全遵循作者说描述的字体样式。
第 91 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.3.2 字段描述符
字段描述符(Field Descriptor),是一个表示类、实例或局部变量的语法符号,它是由
语法产生的字符序列:
FieldDescriptor:
FieldType
ComponentType:
FieldType
FieldType:
BaseType
ObjectType
ArrayType
BaseType:
B
C
D
F
I
J
S
Z
ObjectType:
L Classname ;
ArrayType:
[ ComponentType
所有表示基本类型(BaseType)的字符、表示对象类型(ObjectType)中的字符"L",表
Classname 表示一个类或接口二进制名称的内部格式(§4.2.1)。表示数组类型的有效描述符
表 4.2 基本类型字符解释表
字符 类型 含义
B byte 有符号字节型数
D double 双精度浮点数
第 92 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
F float 单精度浮点数
I int 整型数
J long 长整数
S short 有符号短整数
[ reference 一个一维数组
4.3.3 方法描述符
方法描述符(Method Descriptor)描述一个方法所需的参数和返回值信息:
MethodDescriptor:
( ParameterDescriptor* ) ReturnDescriptor
参数描述符(ParameterDescriptor)描述需要传给这个方法的参数信息:
ParameterDescriptor:
FieldType
返回描值述符(ReturnDescriptor)从当前方法返回的值,它是由语法产生的字符序列:
ReturnDescriptor:
FieldType
VoidDescriptor
void):
VoidDescriptor:
V
如果一个方法描述符是有效的,那么它对应的方法的参数列表总长度小于等于 255,对于实
第 93 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
例如,方法:
的描述符为:
(IDLjava/lang/Thread;)Ljava/lang/Object;
无论 mymethod()是静态方法还是实例方法,它的方法描述符都是相同的。尽管实例方法除
了传递自身定义的参数,还需要额外传递参数 this,但是这一点不是由法描述符来表达的。参数
4.3.4 签名
的类型中。泛型类型、方法描述和参数化类型描述等都属于签名。关于签名的详细说明请参考《Java
语言规范(Java SE 7 版)》。
Java 编译器需要这类信息来实现(或辅助实现)反射(reflection)和跟踪调试功能。
(Type)、字段(Field)
、局部变量(Local Variable)、参数(Parameter)、方法(Method)、
变量类型(type variable)等。这些类型的名字中,不能包含字符"."、";"、"["、"/"、
息编译成对应的签名信息。它描述当前类可能包含的所有的(泛型类型的)形式类型参数,如果
Class 有直接父类和父接口,类签名会列出它们(或将它们参数化)。
ClassSignature:
FormalTypeParametersopt SuperclassSignature
SuperinterfaceSignature*
一个正式的形式类型参数必须有自己的名字和对这个参数的类或接口的限制(bounds),限
制跟在类型名之后。如果形式类型参数的类限定没有给定一个具体定义,那么这个参数类型被默认
定义为 java.lang.Object。
第 94 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
FormalTypeParameters:
< FormalTypeParameter+ >
FormalTypeParameter:
Identifier ClassBound InterfaceBound*
ClassBound:
: FieldTypeSignatureopt
InterfaceBound:
: FieldTypeSignature
SuperclassSignature:
ClassTypeSignature
SuperinterfaceSignature:
ClassTypeSignature
段、参数或局部变量的类型编译成对应的签名信息。
FieldTypeSignature:
ClassTypeSignature
ArrayTypeSignature
TypeVariableSignature
签名必被须明确表达,否则不能准确无误的映射至二进制名,在类的类型签名表示方式中,将内部
形式的名称中的符号“.”替换成“$”。
ClassTypeSignature:
L PackageSpecifieropt SimpleClassTypeSignature
ClassTypeSignatureSuffix* ;
PackageSpecifier:
Identifier / PackageSpecifier*
SimpleClassTypeSignature:
Identifier TypeArgumentsopt
ClassTypeSignatureSuffix:
. SimpleClassTypeSignature
TypeVariableSignature:
T Identifier ;
TypeArguments:
< TypeArgument+ >
TypeArgument:
WildcardIndicatoropt FieldTypeSignature
*
WildcardIndicator:
+
第 95 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
-
ArrayTypeSignature:
[ TypeSignature
TypeSignature:
FieldTypeSignature
BaseType
的形式参数的类型编译成相应的签名信息(或将它们参数化)
。形式参数不包括在 throw 语句中申
明的参数类型,返回类型和方法申明的所有的形式类型参数。
MethodTypeSignature:
FormalTypeParametersopt (TypeSignature*) ReturnType
ThrowsSignature*
ReturnType:
TypeSignature
VoidDescriptor
ThrowsSignature:
^ ClassTypeSignature
^ TypeVariableSignature
MethodTypeSignature 中移除。
对于同一个方法或构造函数,描述符和签名(§4.3.3)并不一定完全匹配,这取决于使用的
未来版本中,Java 虚拟机可能会在启动和链接时对签名加入部分或全部校验。
4.4 常量池
Java 虚拟机指令执行时不依赖与类、接口,实例或数组的运行时布局,而是依赖常量池
(constant_pool)表中的符号信息。
第 96 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
所有的常量池项都具有如下通用格式:
cp_info {
u1 tag;
u1 info[];
}
常量类型 值
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18
4.4.1 CONSTANT_Class_info 结构
CONSTANT_Class_info 结构用于表示类或接口,格式如下:
第 97 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
CONSTANT_Class_info 结构的项的说明:
tag
name_index
name_index 项的值,必须是对常量池的一个有效索引。常量池在该索引处的项必须是
CONSTANT_Utf8_info(§4.4.7)结构,代表一个有效的类或接口二进制名称的内部
形式。
池中的 CONSTANT_Class_info(§4.4.1)结构来引用类数组。对于这些数组,类的名字就是
数组类型的描述符,例如:
int[][]
的名字是:
[[I
Thread[]
的名字是:
[Ljava/lang/Thread;
一个有效的数组类型描述符中描述的数组维度必须小于等于 255。
CONSTANT_InterfaceMethodref_info 结构
字段,方法和接口方法由类似的结构表示:
字段:
第 98 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
方法:
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
接口方法:
CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
这些结构各项的说明如下:
tag
CONSTANT_InterfaceMethodref(11)。
class_index
class_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是
CONSTANT_Class_info(§4.4.1)结构,表示一个类或接口,当前字段或方法是这
个类或接口的成员。
(不能是类)
。CONSTANT_Fieldref_info 结构的 class_index 项的类型既可以是
类也可以是接口。
name_and_type_index
name_and_type_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必
第 99 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
须是 CONSTANT_NameAndType_info(§4.4.6)结构,它表示当前字段或方法的名
字和描述符。
4.3.2)。而 CONSTANT_Methodref_info 和
CONSTANT_InterfaceMethodref_info 中给定的描述符必须是方法描述符(§
4.3.3)。
说明这个方法名是特殊的<init>,即这个方法是实例初始化方法(§2.9),它的返回类
型必须为空。
4.4.3 CONSTANT_String_info 结构
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
CONSTANT_String_info 结构各项的说明如下:
tag
string_index
string_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是
的数值常量:
第 100 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
CONSTANT_Float_info {
u1 tag;
u4 bytes;
}
这些结构各项的说明如下:
tag
bytes
的顺序存储。
第 101 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
的数值常量:
CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
tag
high_bytes 和 low_bytes
按照 Big-Endian 顺序存储。
CONSTANT_Double_info 结构表示的值将按照下列方式来表示,high_bytes 和
①
由于历史原因(译者注:是指 JVM 开发时是处于 32 位机为主流的时代),让 8 字节常量占用 2 个表元素的
空间是一个无奈的选择。
第 102 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.4.6 CONSTANT_NameAndType_info 结构
个结构不同,CONSTANT_NameAndType_info 结构没有标识出它所属的类或接口,格式如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
CONSTANT_NameAndType_info 结构各项的说明如下:
tag
(12)。
name_index
name_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是
CONSTANT_Utf8_info(§4.4.7)结构,这个结构要么表示特殊的方法名<init>(§
2.9),要么表示一个有效的字段或方法的非限定名(Unqualified Name)。
descriptor_index
第 103 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
descriptor_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是
CONSTANT_Utf8_info(§4.4.7)结构,这个结构表示一个有效的字段描述符(§
4.3.2)或方法描述符(§4.3.3)。
4.4.7 CONSTANT_Utf8_info 结构
CONSTANT_Utf8_info 结构用于表示字符串常量的值:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Utf8_info 结构各项的说明如下:
tag
length
bytes[]
一个字符占一个 byte。
码点在范围'\u0001'至'\u007F'内的字符用一个单字节表示:
byte 的后 7 位数据表示一个码点值。
字符为'\u0000'(表示字符'null'),或者在范围'\u0080'至'\u07FF'的字符用一对字
节 x 和 y 表示:
第 104 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
x 和 y 计算字符值的公式为:
x,y 和 z 计算字符值的公式为:
((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f)
味着在我们的编码方式中,补充字符需要 6 个字节来表示,u,v,w,x,y 和 z:
这 6 个字符计算字符值的公式为:
0x10000+((v&0x0f)<<16)+((w&0x3f)<<10)+(y&0x0f)<<6)+(z&0x3f)
(Two-Times-Three-Byte)格式来代替。
4.4.8 CONSTANT_MethodHandle_info 结构
CONSTANT_MethodHandle_info 结构用于表示方法句柄,结构如下:
CONSTANT_MethodHandle_info {
第 105 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
u1 tag;
u1 reference_kind;
u2 reference_index;
}
CONSTANT_MethodHandle_info 结构各项的说明如下:
tag
(15)。
reference_kind
reference_index
reference_index 项的值必须是对常量池的有效索引:
索引处的项必须是 CONSTANT_Fieldref_info(§4.4.2)结构,表示由一个字
段创建的方法句柄。
(REF_invokeStatic)、7(REF_invokeSpecial)或 8
是 CONSTANT_Methodref_info(§4.4.2)结构,表示由类的方法或构造函数
创建的方法句柄。
(§4.4.2)结构,表示由接口方法创建的方法句柄。
(REF_invokeStatic)、7(REF_invokeSpecial)或 9
(REF_invokeInterface),那么方法句柄对应的方法不能为实例初始化(<init>)
方法或类初始化方法(<clinit>)。
第 106 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
对应的方法必须为实例初始化(<init>)方法。
4.4.9 CONSTANT_MethodType_info 结构
CONSTANT_MethodType_info 结构用于表示方法类型:
CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
tag
descriptor_index
descriptor_index 项的值必须是对常量池的有效索引,常量池在该索引处的项必须是
CONSTANT_Utf8_info(§4.4.7)结构,表示方法的描述符(§4.3.3)。
4.4.10 CONSTANT_InvokeDynamic_info 结构
数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。
CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
CONSTANT_InvokeDynamic_info 结构各项的说明如下:
tag
CONSTANT_InvokeDynamic(18)。
bootstrap_method_attr_index
第 107 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.7.21)的 bootstrap_methods[]数组的有效索引。
name_and_type_index
name_and_type_index 项的值必须是对当前常量池的有效索引,常量池在该索引处的
项必须是 CONSTANT_NameAndType_info(§4.4.6)结构,表示方法名和方法描述
符(§4.3.3)。
4.5 字段
段同时具有相同的字段名和描述符(§4.3.2)。
field_info 结构格式如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
field_info 结构各项的说明如下:
access_flags
access_flags 项的值是用于定义字段被访问权限和基础属性的掩码标志。
标记名 值 说明
第 108 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
器自动产生的。
8.3.1.4)。
name_index
name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是
CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的字段的非全限定名(§
4.2.2)。
descriptor_index
descriptor_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必
须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的字段的描述符(§
4.3.2)。
attributes_count
attributes_count 的项的值表示当前字段的附加属性(§4.7)的数量。
attributes[]
任意个关联属性。
第 109 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
Deprecated(§4.7.15), RuntimeVisibleAnnotations(§4.7.16) 和
RuntimeInvisibleAnnotations(§4.7.17)。
4.7.9), RuntimeVisibleAnnotations(§4.7.16) 和
RuntimeInvisibleAnnotations(§4.7.17)结构。
信息(§4.7.1)。
4.6 方法
所有方法(Method),包括实例初始化方法和类初始化方法(§2.9)在内,都由 method_info
method_info 结构格式如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
method_info 结构各项的说明如下:
access_flags
access_flags 项的值是用于定义当前方法的访问权限和基本属性的掩码标志,
标记名 值 说明
第 110 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
ACC_VARARGS 标志是用于说明方法在源码层的参数列表是否变长的。如果是变长的,则
ACC_BRIDGE 标志用于说明这个方法是由编译生成的桥接方法①。
它的标志了(JLS §9.4)。
①
译者注:桥接方法是 JDK 1.5 引入泛型后,为了使 Java 的范型方法生成的字节码和 1.5 版本前的字节码
相兼容,由编译器自动生成的方法。
第 111 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
了 ACC_STRICT 标志,其它的标志都将被忽略。
name_index
name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必须是
CONSTANT_Utf8_info(§4.4.7)结构,它要么表示初始化方法的名字(<init>或
<clinit>),要么表示一个方法的有效的非全限定名(§4.2.2)
descriptor_index
descriptor_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的项必
须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的方法的描述符(§4.3.3)
志被设置时,方法描述符中的最后一个参数必须是数组类型。
attributes_count
attributes_count 的项的值表示这个方法的附加属性(§4.7)的数量。
attributes[]
任意个与之相关的属性。
Exceptions(§4.7.5),Synthetic(§4.7.8),Signature(§4.7.9),
Deprecated(§4.7.15),untimeVisibleAnnotations(§4.7.16),
RuntimeInvisibleAnnotations(§4.7.17),
RuntimeVisibleParameterAnnotations(§4.7.18),
RuntimeInvisibleParameterAnnotations(§4.7.19)和
AnnotationDefault(§4.7.20)结构。
第 112 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.7.9),RuntimeVisibleAnnotations(§4.7.16),
RuntimeInvisibleAnnotations(§4.7.17),
RuntimeVisibleParameterAnnotations(§4.7.18),
RuntimeInvisibleParameterAnnotations(§4.7.19)和
AnnotationDefault(§4.7.20)属性。
信息(§4.7.1)。
4.7 属性
4.5)结构,method_info(§4.6)结构和 Code_attribute(§4.7.3)结构都有使用,所
有属性的通用格式如下:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
无符号索引。常量池在该索引处的项必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示当
前属性的名字。attribute_length 项的值给出了跟随其后的字节的长度,这个长度不包括
其他的自定义属性所使用。
StackMapTable(§4.7.4) 6 50.0
BootstrapMethods(§4.7.21) 7 51.0
解析它们的语义。
RuntimeInvisibleAnnotations,RuntimeVisibleParameterAnnotations,
对于剩余的预定义属性的使用不受限制;如果剩余的预定义属性包含虚拟机可识别的信
息,Class 文件加载器就可以选择使用这些信息,否则可以选择忽略它们。
4.7.1 自定义和命名新的属性
范》中没有定义的属性,不能影响类或接口类型的语义。Java 虚拟机实现必须忽略它不能识别的
自定义属性。
例如,编译器可以定义新的属性用于支持与特定发行者相关(Vendor-Specific)的调式,
些 Class 文件包含的附加调式信息不能被它们所用。
当两个不同的属性使用了相同的属性名且长度也相同,无论虚拟机识别其中哪一个都会引起冲
规定的包命名方式来命名。
本规范在未来的版本中可能会定义增加预定义的属性。
第 115 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.7.2 ConstantValue 属性
Field(§5.5))初始化的一部分。这个过程发生在引用类或接口的类初始化方法(§2.9)执
行之前。
ConstantValue 属性的格式如下:
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
ConstantValue 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值,必须是一个对常量池的有效索引。常量池在该索引
处的项必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“ConstantValue”。
attribute_length
constantvalue_index
constantvalue_index 项的值,必须是一个对常量池的有效索引。常量池在该索引处
字段类型 项类型
long CONSTANT_Long
第 116 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
float CONSTANT_Float
double CONSTANT_Double
int,short,char,byte,boolean CONSTANT_Integer
String CONSTANT_String
4.7.3 Code 属性
Code 属性的格式如下:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Code_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的有效索引,常量池在该索引处的项
必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串“Code”。
第 117 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
attribute_length
max_stack
max_stack 项的值给出了当前方法的操作数栈在运行执行的任何时间点的最大深度(§
2.6.2)。
max_locals
max_locals 项的值给出了分配在当前方法引用的局部变量表中的局部变量个数,包括
调用此方法时用于传递参数的局部变量。
最大索引是 max_locals-1.
code_length
大于 0,即 code[]数组不能为空。
code[]
code[]数组以按字节寻址的方式读入机器内存,如果 code[]数组的第一个字节是按以
考相关的指令描述)。
本规范对关于 code[]数组内容的详细约束有很多,将在后面单独章节(§4.9)中列出。
exception_table_length
exception_table[]
exception_table[]数组的每个成员表示 code[]数组中的一个异常处理器
(Exception Handler)
。exception_table[]数组中,异常处理器顺序是有意义的
(不能随意更改),详细内容见 2.10 节。
exception_table[]数组包含如下 4 项:
①
译者注:请注意,由于部分指令在 code[]数组中存有直接操作数,换句话说,有一些字节码指令的实际长
度是超过一个字节的,因此此处字节数长度 code_length 并不等同于 code[]数组的成员个数。
第 118 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
start_pc 和 end_pc
当程序计数器在范围[start_pc, end_pc)内时,异常处理器就将生效。即设 x 为
个字节,并且以一个 1 个字节长度的指令结束,那么这条指令将不能被异常处理器
所处理。不过编译器可以通过限制任何方法、实例初始化方法或类初始化方法的
handler_pc
数组中某一指令的操作码的有效索引。
catch_type
在该索引处的项必须是 CONSTANT_Class_info(§4.4.1)结构,表示当前异常
处理器指定需要捕捉的异常类型。只有当抛出的异常是指定的类或其子类的实例时,
异常处理器才会被调用。
attributes_count
attributes[]
数量的可选属性与之关联。
(§4.7.12),LocalVariableTable(§4.7.13),LocalVariableTypeTable
第 119 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
(§4.7.14)和 StackMapTable(§4.7.4)属性。
4.7.1)。
4.7.4 StackMapTable 属性
会在虚拟机类加载的类型阶段(§4.10.1)被使用。
式或隐式地指定了一个字节码偏移量,用于表示局部变量表和操作数栈的验证类型
类型检测器(Type Checker)会检查和处理目标方法的局部变量和操作数栈所需要的类型。
本章节中,一个存储单元(Location)的含义是唯一的局部变量或操作数栈项。
述如何从方法的局部变量和操作数栈的存储单元映射到验证类型(Verification Types)。当
描述 Class 文件侧的映射时,我们通常使用的术语是“栈映射帧”,而当描述类型检查器侧的映射
关系时,我们通常使用的术语是“类型状态”。
StackMapTable 属性的格式如下:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
第 120 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
StackMapTable 结构项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的有效索引,常量池在该索引的项处
必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示“StackMapTable”字符串。
attribute_length
number_of_entries
entries[]
每个 stack_map_frame 结构都使用一个特定的字节偏移量来表示类型状态。每个帧类型
在运行时的实际字节码偏移量。使用时帧的字节偏移量计算方法为:前一帧的字节码偏移量
只要保证栈映射帧有正确的存储顺序,在类型检查时我们就可以使用增量偏移量而不是实际的
移量不会重复。
stack_map_frame 结构,那我们就说这条指令拥有一个与之相对应的栈映射帧。
字节用于说明更多信息,这些信息因类型标记的不同而变化。
一个栈映射帧可以包含若干种帧类型(Frame Types):
union stack_map_frame {
same_frame;
same_locals_1_stack_item_frame;
same_locals_1_stack_item_frame_extended;
第 121 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
chop_frame;
same_frame_extended;
append_frame;
full_frame;
}
所有的帧类型,包括 full_frame,它们的部分语义会依赖于前置帧,这点使得确定基准帧
详细信息请参考 methodInitialStackFrame(§4.10.1.3.3)。
的值来表示①。
same_frame {
u1 frame_type = SAME; /* 0-63 */
}
same_locals_1_stack_item_frame {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
verification_type_info stack[1];
}
员个数为 1。
当前帧的 offset_delta 的值需要由 offset_delta 项明确指定。有一个 stack[]
①
译者注:此处描述的“stack”、
“locals”是 StackMapTable 属性中的项,它们与运行时栈帧中的操作
数栈、局部变量表有映射关系,但并非同一样东西。原文中的对它们的描述为“stack”和“operand stack”、
“locals”和“local variables”,译文中,指代属性项时使用 locals[]数组、stack 表来表示,而提到
运行时栈帧时,则会明确翻译为操作数栈、局部变量表,也请读者注意根据上下文注意区分。
第 122 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
same_locals_1_stack_item_frame_extended {
u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED;/* 247 */
u2 offset_delta;
verification_type_info stack[1];
}
为 chop_frame,则说明对应的操作数栈为空,并且拥有和前一个栈映射帧相同的 locals[]数
chop_frame {
u1 frame_type = CHOP; /* 248-250 */
u2 offset_delta;
}
型是 same_frame_extended 类型,则说明当前帧有拥有和前一个栈映射帧的完全相同的
locals[]数组,同时对应的 stack[]数组的成员数量为 0。
same_frame_extended {
u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */
u2 offset_delta;
}
型为 append_frame,则说明对应操作数栈为空,并且包含和前一个栈映射帧相同的 locals[]
append_frame {
u1 frame_type = APPEND; /* 252-254 */
u2 offset_delta;
verification_type_info locals[frame_type - 251];
}
在 locals[]数组中,索引为 0 的(第一个)成员表示第一个添加的局部变量。如果要从条
的话,那就意味着 locals[M]一定是下列结构之一:
Top_variable_info
Integer_variable_info
第 123 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
Float_variable_info
Null_variable_info
UninitializedThis_variable_info
Object_variable_info
Uninitialized_variable_info
的局部变量的索引都不能大于此方法的局部变量表的最大索引值。
在 stack[]数组中,索引为 0 的(第一个)成员表示操作数栈的最底部的元素,之后的成员
Top_variable_info
Integer_variable_info
Float_variable_info
Null_variable_info
UninitializedThis_variable_info
Object_variable_info
Uninitialized_variable_info
引都不能大于此方法的操作数的最大深度。
储单元的验证类型信息。
union verification_type_info {
Top_variable_info;
Integer_variable_info;
Float_variable_info;
Long_variable_info;
Double_variable_info;
Null_variable_info;
UninitializedThis_variable_info;
第 124 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
Object_variable_info;
Uninitialized_variable_info;
}
Top_variable_info {
u1 tag = ITEM_Top; /* 0 */
}
Integer_variable_info {
u1 tag = ITEM_Integer; /* 1 */
}
Float_variable_info {
u1 tag = ITEM_Float; /* 2 */
}
则要求:
不能是最大索引值的局部变量。
按顺序计数的下一个局部变量包含验证类型 ᴛ
如果单元存储是操作数栈成员,则要求:
当前的存储单元不能在栈顶。
靠近栈顶方向的下一个存储单元包含验证类型 ᴛ。
Long_variable_info {
u1 tag = ITEM_Long; /* 4 */
}
变量,则要求:
不能是最大索引值的局部变量。
按顺序计数的下一个局部变量包含验证类型 ᴛ
如果单元存储是操作数栈成员,则要求:
当前的存储单元不能在栈顶。
第 125 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
靠近栈顶方向的下一个存储单元包含验证类型 ᴛ。
Double_variable_info {
u1 tag = ITEM_Double; /* 3 */
}
Null_variable_info {
u1 tag = ITEM_Null; /* 5 */
}
UninitializedThis_variable_info 类型说明存储单元包含验证类型
uninitializedThis。
UninitializedThis_variable_info {
u1 tag = ITEM_UninitializedThis; /* 6 */
}
Object_variable_info {
u1 tag = ITEM_Object; /* 7 */
u2 cpool_index;
}
Uninitialized_variable_info 说明存储单元包含验证类型
Uninitialized_variable_info {
u1 tag = ITEM_Uninitialized /* 8 */
u2 offset;
}
4.7.5 Exceptions 属性
第 126 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
Exceptions 属性格式如下:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
Exceptions_attribute 格式的项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串"Exceptions"。
attribute_length
number_of_exceptions
数量。
exception_index_table[]
exception_index_table[]数组的每个成员的值都必须是对常量池的有效索引。常量
池在这些索引处的成员必须都是 CONSTANT_Class_info(§4.4.1)结构,表示这个
方法声明要抛出的异常的类的类型。
一个方法如果要抛出异常,必须至少满足下列三个条件中的一个:
要抛出的是在 exception_index_table[]数组中申明的异常类或其子类的实例。
4.7.6 InnerClasses 属性
第 127 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
CONSTANT_Class_info 成员,且这个成员所表示的类或接口不属于任何一个包,那么 C 的
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{ u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
}
attribute_name_index
attribute_name_index 项的值必须是一个对常量池的有效引用。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串"InnerClasses"。
attribute_length
number_of_classes
classes[]
则每个类或接口在 classes[]数组中都有一个成员与之对应。
这条规则暗示着内部类或内部接口成员在其被定义的外部类(Enclosing Class)中都会包
classes[]数组中每个成员包含以下 4 个项:
inner_class_info_index
inner_class_info_index 项的值必须是一个对常量池的有效索引。常量池在该索引
第 128 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
外 3 项都用于描述 C 的信息。
outer_class_info_index
否则这个项的值必须是对常量池的一个有效索引,常量池在该索引处的项必须是
CONSTANT_Class_info(§4.4.1)结构,代表一个类或接口,C 为这个类或接口的
成员。
inner_name_index
个项的值必须是对常量池的一个有效索引,常量池在该索引处的项必须
inner_class_access_flags
表 4.8 内部类访问全和基础属性标志
标记名 值 含义
outer_class_info_index 项的值也必须为 0。
Class 文件的一致性。
4.7.7 EnclosingMethod 属性
个 EnclosingMethod 属性。
EnclosingMethod 属性格式如下:
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index
u2 method_index;
}
EnclosingMethod_attribute 结构的项说明如下:
attribute_name_index
attribute_name_index 项的值必须是一个对常量池的有效索引。常量池在该索引处
的项必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
"EnclosingMethod"。
attribute_length
attribute_length 项的值固定为 4。
class_index
class_index 项的值必须是一个对常量池的有效索引。常量池在该索引出的项必须是
CONSTANT_Class_info(§4.4.1)结构,表示包含当前类声明的最内层类。
method_index
第 130 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
如果当前类不是在某个方法或初始化方法中直接包含(Enclosed),那么
常量池在该索引处的成员必须是 CONSTANT_NameAndType_info(§4.4.6)结构,
4.7.8 Synthetic 属性
的例外是某些与人工实现无关的、由编译器自动产生的方法,也就是说,Java 编程语言的默认的
实例初始化方法(无参数的实例初始化方法)、类初始化方法,以及 Enum.values()和
Synthetic 属性的格式如下:
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Synthetic 结构的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引,常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串“Synthetic”。
attribute_length
attribute_length 项的值固定为 0。
第 131 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.7.9 Signature 属性
员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized
Signature 属性格式如下:
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}
Signature_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是一个对常量池的有效索引。常量池在索引处的
成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串“Signature”。
attribute_length
signature_index
signature_index 项的值必须是一个对常量池的有效索引。常量池在该索引处的项必
须是 CONSTANT_Utf8_info(§4.4.7)结构,表示类签名或方法类型签名或字段类
类型签名。
4.7.10 SourceFile 属性
第 132 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
SourceFile 属性格式如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
SourceFile_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是一个对常量池的有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串“SourceFile”。
sourcefile_index
sourcefile_index 项的值必须是一个对常量池的有效索引。常量池在该索引处的成员
必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个字符串。
源文件所在目录的目录名,也不包括源文件的绝对路径名。平台相关(绝对路径名等)的
附加信息必须是运行时解释器(Runtime Interpreter)或开发工具在文件名实际使
用时提供。
4.7.11 SourceDebugExtension 属性
SourceDebugExtension 属性的格式如下:
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}
SourceDebugExtension_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
第 133 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“SourceDebugExtension”。
attribute_length
debug_extension[]
4.7.12 LineNumberTable 属性
的行一一对应。
LineNumberTable 属性格式如下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
LineNumberTable_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
第 134 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
“LineNumberTable”。
attribute_length
line_number_table_length
数。
line_number_table[]
line_number_table[]数组的每个成员都表明源文件中行号的变化在 code[]数组中
都会有对应的标记点。line_number_table 的每个成员都具有如下两项:
start_pc
line_number
line_number 项的值必须与源文件的行数相匹配。
4.7.13 LocalVariableTable 属性
个 LocalVariableTable 属性。
LocalVariableTable 属性格式如下:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
第 135 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
u2 index;
} local_variable_table[local_variable_table_length];
}
LocalVariableTable_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“LocalVariableTable”。
attribute_length
local_variable_table_length
的成员的数量。
local_variable_table[]
local_variable_table[]数组的每一个成员表示一个局部变量的值在 code[]数组
中的偏移量范围。它同时也是用于从当前帧的局部变量表找出所需的局部变量的索引。
local_variable_table[]数组每个成员都有如下 5 个项:
start_pc, length
所有给定的局部变量的索引都在范围[start_pc, start_pc+length)中,即从
start_pc(包括自身值)至 start_pc+length(不包括自身值)。start_pc 的
数组的有效索引,code[]数组在该索引处必须是一条指令的操作码,要么是刚超过
code[]数组长度的最小索引值。
name_index
name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的成员必
须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个局部变量的有效的非全
限定名(§4.2.2)。
descriptor_index
descriptor_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的
第 136 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示源程序中局部变量类
型的字段描述符(§4.3.2)。
index
4.7.14 LocalVariableTypeTable 属性
个 LocalVariableTable 属性。
LocalVariableTypeTable 提供签名信息而不是描述符信息。这仅仅对泛型类型有意义。泛型
LocalVariableTypeTable 属性格式如下:
LocalVariableTypeTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_type_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
}local_variable_type_table[local_variable_type_table_length
];
}
LocalVariableTypeTable_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
第 137 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“LocalVariableTypeTable”。
attribute_length
local_variable_type_table_length
local_variable_type_table_length 项的值给出了
local_variable_type_table[]数组的成员的数量。
local_variable_type_table[]
local_variable_type_table[]数组的每一个成员表示一个局部变量的值在 code[]
数组中的偏移量范围。它同时也是用于从当前帧的局部变量表找出所需的局部变量的索
引。local_variable_type_table[]数组每个成员都有如下 5 个项:
start_pc, length
所有给定的局部变量的索引都在范围[start_pc, start_pc+length)中,即从
。start_pc 的值必
start_pc(包括自身)至 start_pc+length(不包括自身)
的有效索引,code[]数组在该索引处必须是一条指令的操作码,要么是刚超过
code[]数组长度的最小索引值。
name_index
name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的成员必
须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个局部变量的有效的非全
限定名(§4.2.2)。
signature_index
signature_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的
成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示给源程序中局部变量
类型的字段签名(§4.3.4)。
index
第 138 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.7.15 Deprecated 属性
或 method_info(§4.6)结构的属性表中。类、接口、方法或字段都可以带有为 Deprecated
属性,如果类、接口、方法或字段标记了此属性,则说明它将会在后续某个版本中被取代。在运行
避免使用这些类、接口、方法或字段,选择其他更好的方式。Deprecated 属性的出现不会修改
类或接口的语义。
Deprecated 属性格式如下:
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Deprecated_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串“Deprecated”。
attribute_length
attribute_length 项的值固定为 0。
4.7.16 RuntimeVisibleAnnotations 属性
field_info(§4.5)或 method_info(§4.6)结构的属性表中。
第 139 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
RuntimeVisibleAnnotations 属性格式如下:
RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeVisibleAnnotations_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“RuntimeVisibleAnnotations”。
attribute_length
attribute_length 项的值由当前结构的运行时可见注解的数量和值决定。
num_annotations
num_annotations 项的值给出了当前结构表示的运行时可见注解的数量。每个程序元
annotations[]
annotations[]数组的每个成员的值表示一个程序元素的唯一的运行时可见注解。
annotation 结构的格式如下:
annotation {
u2 type_index;
u2 num_element_value_pairs;
{ u2 element_name_index;
element_value value;
} element_value_pairs[num_element_value_pairs]
}
annotation 结构各项的说明如下:
type_index
type_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的成
员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个字段描述符,
第 140 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
num_element_value_pairs
解的键值对(键值对的格式为:元素-值)的数量,即 element_value_pairs[]
数组成员数量。需要注意的是,在单独一个注解中可能含有数量最多为 65535
个键值对。
element_value_pairs[]
element_value_pairs[]数组的每一个成员的值对应当前 annotation 结
构表示的注解中的一个唯一的键值对。element_value_pairs 的成员包含如
下两个项。
element_name_index
element_name_index 项的值必须是对常量池的一个有效索引。常量池
在该索引处的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表
示一个有效的字段描述符(§4.3.2),这个字段描述符用于定义当前
element_value_pairs 的成员表示的注解的注解名。
value
值。
4.7.16.1 element_value 结构
-值”的键值对中的值。它被用来描述所有注解(包括 RuntimeVisibleAnnotations、
RuntimeInvisibleAnnotations、RuntimeVisibleParameterAnnotations 和
untimeInvisibleParameterAnnotations)中涉及到的元素值。
element_value 的结构格式如下:
element_value {
u1 tag;
①
译者注:“Discriminated Union”是一种数据结构,用于表示若干种具有独立特征的同类项集合。
第 141 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
union {
u2 const_value_index;
{ u2 type_name_index;
u2 const_name_index;
} enum_const_value;
u2 class_info_index;
annotation annotation_value;
{ u2 num_values;
element_value values[num_values];
} array_value;
} value;
}
element_value 结构各项的说明如下。
tag
tag 值 元素类型
s String
e enum constant
c class
@ annotation type
[ array
value
个联合体结构的哪一项会被使用:
type_name_index
type_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的
成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的字段描述
类型的内部形式的二进制名称(§ 4.2.1)。
第 142 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
const_name_index
const_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处的
成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示一个有效的字段描述
类型的简单名称。
class_info_index
的值必须是对常量池的一个有效索引。常量池在该索引处的成员必须是
CONSTANT_Utf8_info(§4.4.7)结构,表示返回描述符(§4.3.3)的类型,
annotation_value
结构表示一个内部的注解(Nested Annotation)。
array_value
num_values
values
类型的一个元素值。
4.7.17 RuntimeInvisibleAnnotations 属性
过特殊的实现相关的方式(譬如特定的命令行参数)收到才会(为反射的 API)使用这些注解。
RuntimeInvisibleAnnotations 属性格式如下:
RuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeInvisibleAnnotations_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“RuntimeInvisibleAnnotations”。
attribute_length
attribute_length 项的值由当前结构的运行时非可见注解的数量和值决定。
num_annotations
num_annotations 项的值给出了当前结构表示的运行时可见注解的数量。每个程序元
annotations[]
annotations[]数组的每个成员的值表示一个程序元素的唯一的运行时可见注解。
4.7.18 RuntimeVisibleParameterAnnotations 属性
第 144 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
API 使用。
RuntimeVisibleParameterAnnotations 格式如下:
RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
RuntimeVisibleParameterAnnotations_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“RuntimeVisibleParameterAnnotations”。
attribute_length
attribute_length 项的值由对应方法的参数数量,参数的运行时可见注解和它们的值
所决定。
num_parameters
法参数的数量(这些信息可以从方法描述符中获得)。
parameter_annotations
parameter_annotations[]数组中每个成员的值表示一个的参数的所有的运行时可
见注解。它们的顺序和方法描述符表示的参数的顺序一致。每个
parameter_annotations 成员都包含如下两项:
num_annotations
处的元素的可见注解的数量。
第 145 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
annotations[]
annotations[]数组中的每个成员的值表示 parameter_annotations[]数组
在当前索引处的元素的一个唯一的可见注解。
4.7.19 RuntimeInvisibleParameterAnnotations 属性
RuntimeInvisibleParameterAnnotations 属性和
RuntimeVisibleParameterAnnotations 属性类似,区别是
RuntimeInvisibleParameterAnnotations 属性的格式如下:
RuntimeInvisibleParameterAnnotations 格式如下:
RuntimeInvisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{ u2 num_annotations;
annotation annotations[num_annotations];
} parameter_annotations[num_parameters];
}
RuntimeInvisibleParameterAnnotations_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“RuntimeInvisibleParameterAnnotations”。
attribute_length
第 146 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
attribute_length 项的值由对应方法的参数数量,参数的运行时非可见注解和它们的
值所决定。
num_parameters
法参数的数量(这些信息可以从方法描述符中获得)。
parameter_annotations
parameter_annotations[]数组中每个成员的值表示一个的参数的所有的运行时非
可见注解。它们的顺序和方法描述符表示的参数的顺序一致。每个
parameter_annotations 成员都包含如下两项:
num_annotations
元素的非可见注解的数量。
annotations[]
annotations[]数组中的每个成员的值表示 parameter_annotations[]数组
在当前索引处的元素的一个唯一的非可见注解。
4.7.20 AnnotationDefault 属性
构的属性表中,这些结构表示注解类型的元素。AnnotationDefault 属性用于保存
用。
AnnotationDefault 属性格式如下:
AnnotationDefault_attribute {
u2 attribute_name_index;
u4 attribute_length;
element_value default_value;
}
第 147 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
AnnotationDefault_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“AnnotationDefault”。
attribute_length
attribute_length 项的值由默认值决定。
default_value
4.7.21 BootstrapMethods 属性
BootstrapMethods 属性格式如下:
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{ u2 bootstrap_method_ref;
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];
} bootstrap_methods[num_bootstrap_methods];
}
BootstrapMethods_attribute 结构各项的说明如下:
attribute_name_index
attribute_name_index 项的值必须是对常量池的一个有效索引。常量池在该索引处
第 148 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
的成员必须是 CONSTANT_Utf8_info(§4.4.7)结构,表示字符串
“BootstrapMethods”。
attribute_length
attribute_length 项的值由默认值决定①。
num_bootstrap_methods
限定符的数量。
bootstrap_methods[]
bootstrap_methods[]数组的每个成员包含一个指向 CONSTANT_MethodHandle 结
构的索引值,它代表了一个引导方法。还包含了这个引导方法静态参数的序列(可能为空)。
bootstrap_methods 每项必须包含以下 3 项:
bootstrap_method_ref
bootstrap_method_ref 项的值必须是一个对常量池的有效索引。常量池在该索
(REF_invokeStatic)或 8(REF_newInvokeSpecial)
(§5.4.3.5),否则
在 invokedynamic 指令解析调用点限定符时,引导方法会执行失败。
num_bootstrap_arguments
员的数量。
bootstrap_arguments[]
bootstrap_arguments[]数组的每个成员必须是一个对常量池的有效索引。常量
池在该索引出必须是下列结构之一:
CONSTANT_String_info,CONSTANT_Class_info、
CONSTANT_Integer_info,CONSTANT_Long_info、
①
译者注:这里原文为“attribute_length item is thus dependent on the default value”,
但从上下文来说词不达意,笔者怀疑是原作者偷懒,误将前一节“AnnotationDefault 属性”中的
attribute_length 项的描述内容复制到这里。
第 149 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
CONSTANT_Float_info,CONSTANT_Double_info、
CONSTANT_MethodHandle_info 或 CONSTANT_MethodType_info。
4.8 格式检查
长度,文件的尾部不能缺少或者多出额外的字节,常量池必须不含任何被规范未预定义的信息。
原因,格式检查与字节码验证曾经混淆在一起,因为它们两者都是完整性检查的一种形式。
Java 虚拟机将普通方法、实例初始化方法(§2.9)或类和接口初始化方法(§2.9)的代
Code_attribute 结构的内容相关的约束情况。
4.9.1 静态约束
是如何排列的,某些特殊的指令必须带有哪些操作数等等。
code[]数组中的指令的约束情况如下:
3. code[]数组中第一条指令的操作码是从数组中索引为 0 处开始。
范的操作码的指令一定不允许出现在 code[]数组中。
code[]数组中。
6. 对于 code[]数组中除最后一条指令外的其它指令来说,后一条指令的操作码的索引等于
当前指令操作码的索引加上当前指令的长度(包含指令带有的操作数)
。wide 指令在这
7. code[]数组中最后一条指令的最后一个字节的索引必须等于 code_length 值减 1。
code[]数组中的指令的操作数的约束情况如下:
所有跳转和分支指令(jsr、jsr_w、goto、goto_w、ifeq、ifne、ifle、iflt、
ifgt、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmple、if_icmplt、
if_icmpge、if_icmpgt、if_acmpeq、if_acmpne)的跳转目标必须是本方法内某
下列规则:
第 151 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
CONSTANT_Float 或 CONSTANT_String。
CONSTANT_Float,CONSTANT_String 或 CONSTANT_Class。
CONSTANT_Float,CONSTANT_String,CONSTANT_Class,
CONSTANT_MethodType 或 CONSTANT_MethodHandle。
常量池索引之后那个常量池项一定不能被独立使用。
constant_pool 表内的一个有效索引。由这些索引所引用的常量池项必须是
CONSTANT_Fieldref 类型。
invokedynamic 指令的第三和第四个操作数字节必须是 0。
只有 invokespecial 指令可以调用实例初始化方法(§2.9)。其他所有以’<’
(’\u003c’)字符开头的方法都不能再被方法调用指令所调用。需要特别说明的是,被
Java 虚拟机自己来隐式调用。
的第四个操作数字节必须是 0。
第 152 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
度的数组。那就是说,multianewarray 指令创建的数组的维度可以比操作数所示的小
(注:多维数组中某一维度的数组长度为 0 的情况),但是它所创建的数组维度绝对不能
能为 0。
(5),T_FLOAT(6),T_DOUBLE(7),T_BYTE(8),T_SHORT(9),T_INT(10)
和 T_LONG(11)。
位操作数必须是非负整数且不能大于值 max_locals-1。
所有 iload_<n>,fload_<n>,aload_<n>,istore_<n>,fstore_<n>和
astore_<n>指令的显式索引必须小于等于值 max_locals–1。
max_locals–2。
所有 lload_<n>,dload_<n>,lstore_<n>和 dstore_<n>指令的显式索引必须小
于或等于值 max_locals–2。
或等于值 max_locals–2。
第 153 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.9.2 结构化约束
所有指令都必须可以在操作数栈和局部变量表中,基于合适的类型及参数下正常执行,且
如果某条指令可以通过几个不同的执行路径执行,在执行指令之前,不管最终采用哪条执
行路径,操作数栈必须有相同的深度(§2.3.4)。
置或割裂。对于这样的局部变量值对也绝不可以分开单独使用。
访问。
在执行过程中,不允许从操作数栈中取出比它包含的全部数据还多的数据。
每个 invokespecial 指令应该指明一个实例初始化方法(§2.9),这个实例初始化方
法或是当前类的方法,或是当前类某个父类的方法。
如果 invokespecial 所指明的实例初始化方法的类不是当前类或父类的方法,并且操
从这个实例所属的类中确定一个实例初始化方法。
在调用实例初始化方法(§2.9)时,那个还未被初始化的实例必须存放在操作数栈上适
当的位置。如果一个实例已经被初始化过,那就不允许再调用它的实例初始化方法。
在调用任意实例的方法或访问任意实例变量之前,那个包含此实例方法或实例变量的实例
对象必须是已经被初始化过的。
在操作数栈或局部变量表上不允许有未被初始化的类变量作为回向分支(Backwards
Branch)①的目标,除非在分支指令上有特殊的未初始化实例类型已经在分支的目标上
与自身归并(§4.10.2.4)。
被异常处理器(§4.10.2.4)所保护的代码中,局部变量表内不允许出现未被初始化的
①
译者注:如果一个分支的结果指向指令流中一个在此指令之前的目标,则称为回向分支。
第 154 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
实例。
来引用该类中的其它初始化方法,要么通过 super()来调用它的直接父类的实例初始化
方法来初始化实例。然而,在调用任何实例初始化方法之前,声明在当前类中的实例字段
应当被赋予默认值。
所有方法调用的参数都必须与方法描述符(§4.3.3)相兼容(JLS §5.3)。
方法调用指令的目标实例的类型必须与指令所指定的类或接口的类型相兼容(JLS
类型必须与当前类相兼容。
所有返回指令都必须与方法的返回类型匹配。如果方法的返回类型是 boolean,byte,
的类型相兼容(JLS §5.2)。所有的实例初始化方法,类或接口初始化方法和声明返回
protected 字段,那么正在被访问的实例必须与当前类或当前类的子类是相同的。如果
protected 方法,那么正在被访问的实例必须与当前类或当前类的子类是相同的。
类型相兼容(JLS §5.2)。
述符相兼容(§4.3.2)
。如果描述符类型是 boolean,byte,char,short 或 int,
与描述符类型相兼容(JLS §5.2)
。
数组的组件类型(Component Type,§2.4)也必须是引用类型。
第 155 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
程序执行时,不允许执行超过 code[]数组末端。
返回地址(returnAddress 类型值)不可以从局部变量表中加载。
当某程序子片段(Subroutine)已经出现在其他程序子片段调用链上时,跳转入该程序
中嵌套,但这并不会无限递归)。
它作为返回地址。
在前面章节中提到了许多规则和约束,Java 语言编译器需要遵循这些规则来生成代码,以保
Class 文件都来自于正确实现的编译器或者有正确的格式。某些应用程序,譬如网络浏览器可能
是否是来自于想恶意破坏虚拟机的人。
如果仅做编译时检查的话还存在另外一个问题:版本偏差(Version Skew)。假设有一个用
TradingClass 的内容很可能在上次编译之后又发生了变化,从而导致无法与以前存在的二进制
内容向兼容。如原来存在的方法可能被删除、返回值类型可能被修改、字段的类型被改变、字段从
验。
链接期校验还有助于增强解释器的执行性能,因为解释器在运行期无需再对每个执行指令进行
保以下内容:
操作数栈不会发生上限或下线溢出。
所有局部变量的使用和存储都是有效的。
所有 Java 虚拟机指令都拥有正确的参数类型。
包括如下内容:
确保常量池之中所有字段引用和方法引用都有有效的名称、类型和方法描述符。
请注意,上述检查不能保证给定的字段或者方法在给定的类中实际存在,也不能保证类型描述
符中引用的是一定是一个真实存在的类,而只能保证这些项在形式上是正确的。更多相关的检查会
在字节码本身已被验证过之后的解析阶段进行。
Java 虚拟机可以使用两种不同的检查策略:类型检查验证和类型推导验证。对于版本号大于
Card 平台的虚拟机要遵循它们自身的规范。
4.10.1 类型检查验证
规则和方法原型。由于大量方法原型的存在,这部分内容翻译出来意义不大,所以本小节是全规范
之中唯一未进行翻译的部分,请有需要的读者阅读规范原文。
第 157 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.10.2 类型推导验证
等于 49.0)需要使用类型推断的方式来验证。
4.10.2.1 类型推断的验证过程
证器必须确保程序中无论在任何执行时间、无论是选择哪条执行路径,都必须遵循以下规则:
操作数栈的深度及所包含的值的类型总是相同。
在明确确定要局部变量的数据类型之前,不能访问该局部变量。
方法调用必须携带适当的参数。
对字段所赋的值一定会与对象的类型相符。
所有的操作码在操作数栈和局部变量表中都有适当类型的参数。
对于由异常处理器所保护的代码,局部变量中不允许出现未初始化的类实例。然而,未初
始化的类实例可以出现在由异常处理器保护的代码的操作数栈中,因为当异常被抛出时,
操作数栈的内容就会被丢弃。
有时候考虑到效率,验证器中一些关键测试会被延迟至方法的代码第一次被真正调用时才执
例如,某个方法调用另一个方法,且被调用的方法返回了一个类 A 的实例,这个实例会赋值
给与它相同类型的一个字段,这时验证器不会耗费时间去检查 A 类是否真实存在。然而,如果这
4.10.2.2 字节码验证器
Class 文件中每个方法的代码都要被单独地验证。首先,组成代码的字节序列被会分隔成一
系列指令,每条指令在 code[]数组中的起始位置索引将被记录在另外的数组中。然后,验证器再
第 158 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
次遍历代码并分析每条指令。经过这次遍历之后,会生成一个数组结构用于存储方法中每个 Java
虚拟机指令的相关信息。如果有必要的话,验证器会检查每条指令的操作数,以确保它们是合法的。
例如将会检查以下内容:
方法分支跳转一定不能超过 code[]数组的范围。
方法中如果某个分支指向了一条指令中间,那这种行为是非法的。
方法会显式声明自己局部变量表的大小,指令所访问或修改的局部变量索引绝不能大于这
个限制值。
对常量池项的引用必须要求该项符合预期的类型。譬如,getfield 指令只能引用字段
项。
代码执行不能终止于指令的中部。
代码执行不能超出 code[]数组的尾部。
对于所有的异常处理器,由处理器所保护的代码的起点应该是指令的起始处,终点应在代
码的尾部。起点必须在终点之前。异常处理器保护的代码必须起始于一个有效的指令,且
对于方法中的每条指令来说,在指令执行之前,验证器会记录下此时操作数栈和局部变量表中
的内容。对于操作数栈,验证器需要知道栈的深度及里面每个值的类型。对于每个局部变量,它需
要知道当前局部变量的值的类型,如果当前值还没有被初始化,那么它需要知道这是一个未使用或
未知的值。在确定操作数栈上值的类型时,字节码验证器不需要区分到底是哪种整型(例如 byte,
short 和 char)。
接下来真正的数据流分析器(Data-Flow Analyzer)被初始化了。在方法的第一条指令执
行之前,局部变量表中存放方法参数的局部变量就已根据方法描述符中描述的参数数据类型初始化
好,此时操作数栈为空,其它的局部变量包含非法(不可使用)的值。对于那些还没有被检查的指
令,分析器不保存与它们有关的操作数栈或局部变量表信息。
接着,数据流分析器可以开始运作了。它为每条指令都设置一个“变更位”
(Changed Bit),
用来表示指令是否需要被检测。最开始时只有方法的第一条指令设置了变更位。数据流分析器执行
流程如下面的循环:
1. 选取一个变更位被设置过的指令。如果不能选取到变更位被设置过的指令,那么表示方法
第 159 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
被成功地验证过。否则,关闭选取的指令的变更位。
2. 通过下述的方式来模拟操作数栈和局部变量表中指令的行为:
如果指令使用操作数栈中的值,就得确保操作数栈中有足量的数据且栈顶值的类型是
合适的。否则验证失败。
如果指令使用局部变量中的值,就得确保那个特定变量的值符合预期的类型。否则验
证失败。
如果指令需要往操作数栈存储数据,就得确保操作数栈中有充足的空间来容纳新值,
并在模拟的操作数栈的栈顶增加新值的类型。
如果指令试图修改局部变量中的值,记录下当前局部变量包含的新值的类型。
3. 检查当前指令的后续指令。后续指令可以是下述的某一种:
续指令就是下一条指令。如果此时超出方法的最后一条指令,那么验证失败。
条件或非条件的分支或转换指令的目标指令。
当前指令的任何异常处理器。
4. 在继续执行下一条指令之前,需要将当前指令执行结束后操作数栈和局部变量表的状态合
并起来。在处理控制转移到异常处理器的情况时,操作数栈上只包含异常处理器的那个异
常类型对象。为此操作数栈上必须有充足的空间来容纳这个值,就如同有指令将值压到栈
中一样。
如果这是后续指令是第一次被访问到,在指令执行之前,将采用第 2 和第 3 步中描
述的规则,把操作数栈和局部变量表的操作记录为分析器模拟的操作数栈和局部变量
表的初始值。为后续指令设置变更位。
如果后续指令之前执行过,只要将操作数栈和局部变量表中按照第 2 和 3 步的规则
计算出值合并入模拟的操作数栈和局部变量表中即可。如果之前对这些值有更改过,
那么也得设置变更位。
5. 继续第 1 步。
合并两个操作数栈之前,每个栈上的值的数量必须是相同的。栈上每个元素的类型也必须是相
①
译者注:最小公共父类是指类继承体系中,两个类型父类中最底层的那个父类
第 160 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
为 Object 类型是所有类或接口的父类。如果两个操作数栈不能合并,那么验证失败。
合并两个局部变量表状态之前,局部变量对的数量应该是相同的。如果两个类型不相同,除非
小公共父类的实例。
功地验证了。
某些指令和数据类型会使数据流分析器的行为变得更为复杂,接下来我们详细介绍这方面的内
容。
或 dup2 指令来处理。
①
译者注:Java 虚拟机指令集中类型无关的指令一般都是操作数栈指令,譬如 pop、dup 等。
第 161 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
4.10.2.4 实例初始化方法与创建对象
创建一个新的类实例需要多个步骤的处理过程。例如下面语句:
... ...
new myClass(i, j, k)
... ...
它的实现可能像这样:
... ...
new #1 // Allocate uninitialized space for myClass
dup // Duplicate object on the operand stack
iload_1 // Push i
iload_2 // Push j
iload_3 // Push k
invokespecial #5 // Invoke myClass.<init>
... ...
上述指令序列在操作数栈栈顶上保留着最新创建和初始化过的对象引用(代码编译成 Java 虚
类 myClass 的实例初始化方法(§2.9)可以看到新建但未初始化的对象,并且这个对象以
字段赋值。
在为实例方法做数据流分析时,验证器初始化局部变量索引 0 处为当前类的一个对象。在分
析实例初始化方法时,局部变量 0 处包含一个特殊类型用来显示此未初始化对象。在调用这个对
象的恰当的实例初始化方法(由当前的类或它的父类)后,由当前的类类型在验证器的操作数栈和
局部变量表模型中都放置此特殊类型。验证器会拒绝那些对象初始化之前使用对象及初始化多个对
象的代码。除此之外,它还要确保在调用正常的方法之前都得事先调用方法所在类或它的直接父类
的实例初始化方法。
与此类似的是,一个特殊类型会被创建并入栈到验证器的操作数栈模型中用来表示 Java 虚拟
建,但是还未执行过初始化方法的实例。当一个未初始化类实例的实例初始化方法被调用之后,所
有使用这个特殊类型的地方都被替换成为真实的类实例的类型。这种类型的改变可能影响到数据流
分析器对后续指令的分析过程。
第 162 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
在存储这个特殊类型的时候,指令序号必须一起存储起来。因为操作数栈中有可能会同时出现
多于 1 个实例已被创建,但未被初始化的情况,譬如下面这个例子:
栈或局部变量表中的代表那个被初始化类实例的对象将会被替换掉。
一个有效地指令集序列里,在回向分支(Backwards Branch)的操作数栈或局部变量表中,
象。否则,验证器将可能会被一些有歧义的代码片段所干扰,误以为某些类实例在它需要被初始化
的时候已经被初始化过,而实际上,这个被初始化的实例已经通过前面某个循环被创建了。
用将两种特殊指令:jsr(“跳转到程序子片段”)和 ret(“程序子片段返回”)组合的方式来生成
地址存放在局部变量中,在程序子片段执行结束时,ret 指令从局部变量中取回返回地址并将执
行的控制权交给返回地址处的指令。
①
译者注:这里写“可以使用 jsr 和 ret 指令”,但在 Oracle JDK 的编译器里,很久之前(JDK 1.4.2)
就已经不再使用这两条指令来实现 try-finally 语法结构了。
第 163 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
1. 如果有返回值的话,将返回值保存在局部变量中。
3. 在 finally 执行完成后,返回事先保存在局部变量中的值。
1. 将异常保存在局部变量中。
句块”的内容。
finally 语句中的代码也给验证器带来了一些特殊的问题。通常情况下,如果可以通过多条
路径抵达一个特殊的指令或是由这些路径找到的某个特殊包含一些不兼容的值的局部变量,那么这
的情况:
如果从异常处理器处调用,就会带着一个包含异常实例的局部变量。
指令的后续指令状态后,验证器还得注意到:异常处理器的局部变量中应该有一个异常实例,
return 代码期望的局部变量是返回值而不是未确定的值。
验证 finally 语句中的代码是很复杂的,但几个基本的思路如下:
finally 代码来说,列表的长度应该大于 1。
量(Bit Vector)记录着所有对局部变量的访问及修改。
执行 ret 指令就意味着从程序子片段中返回,这应该是唯一的一条从程序子片段中返回
第 164 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
值定义如下:
如果位向量(前面定义过)表明局部变量在程序子片段中被访问或修改过,那么就使
位 constant_pool_count 字段的值决定。这限制了单个类或接口的复杂度。
值就会相应地减少。
包含从父类或父接口中继承下来的字段。
的值不包含从父类或父接口中继承下来的方法。
interfaces_count 项的值所决定。
用 max_locals 中的两个单元,所以使用这些类型时,操作数栈的限制的最大值就会相
应地减少。
第 165 页 / 共 387 页
Java 虚拟机规范 — 第 4 章 Class 文件格式
决定。
用是针对实例或接口方法,那么这个限制也包含着占有一个单元的 this。注意对于定义
以如果有这些类型的话,最终的限制的最大值将会变小。
9. 字段和方法名称、字段和方法描述符以及其它常量字符串值(由 ConstantValue 属性
符串的字节数量而不是被编码的字符数量。UTF-8 一般用两个或三个字节来编码字符,
因此,当字符串中包含多字节字符时,会受到更大的约束。
第 166 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
第 5 章 加载、链接与初始化
Java 虚拟机动态地加载、链接与初始化类和接口。加载是根据特定名称查找类或接口类型的
二进制表示(Binary Representation),并由此二进制表示创建类或接口的过程。链接是为
初始化是指执行类或接口的初始化方法<clinit>(§2.9)
。
5.1 运行时常量池
结构,像传统编程语言实现中的符号表一样有很多用途。
造运行时常量池。运行时常量池中的所有引用最初都是符号引用。这些符号引用来自于类或接口的
二进制表示的如下结构中:
某个类或接口的符号引用来自于类或接口二进制表示中的 CONSTANT_Class_info 结构
(§4.4)
。这种引用提供的类或接口的名称如同 Class.getName()方法返回值的格式一样,
也就是说:
对于非数组的类或接口,那就是类或接口的二进制名称(§4.2.1)。
表示:
来表示。
第 167 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
格式来理解。
类或接口的某个字段的符号引用来自于类或接口二进制表示中的
CONSTANT_Fieldref_info 结构(§4.4.2)。这种引用包含了字段的名称和描述符,及指
向字段所属类或接口的符号引用。
类中某个方法的符号引用来自于类或接口二进制表示中的 CONSTANT_Methodref_info 结
构(§4.4.2)。这种引用提供方法的名称和描述符,及指向方法所属类的符号引用。
接口的某个方法的符号引用来自于类或接口二进制表示中的
CONSTANT_InterfaceMethodref_info 结构(§4.4.2)
。这种引用提供接口方法的名称
和描述符,及指向方法所属接口的符号引用。
方法句柄(Method Handle)的符号引用来自于类或接口二进制表示中的
CONSTANT_MethodHandle_info 结构(§4.4.8)。
方法类型(Method Type)的符号引用来自于类或接口二进制表示中的
CONSTANT_MethodType_info 结构(§4.4.9)。
CONSTANT_InvokeDynamic_info 结构(§4.4.10)。这种引用包含了:
来提供服务。
一系列符号引用(到类、方法类型和方法句柄)、字符常量和运行时常量(如 Java 原生
数值类型的值),它们将作为静态参数(Static Arguments)提供给引导方法。
调用方法的名称与描述符
第 168 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
Java 语言需要全局统一的字符常量(这就意味着如果不同字面量(Literal)包含着相
必须返回 true。
列。
String 实例的引用。
其它运行时常量值来自于类或接口二进制表示的 CONSTANT_Interger_info、
CONSTANT_Float_info、CONSTANT_Long_info 或是 CONSTANT_Double_info 结构
(§4.4.4,§4.4.5)
。请注意,这里 CONSTANT_Float_info 结构的值以 IEEE 754 单
(§4.4.4,§4.4.5)
。来自这些结构的运行时常量值必须可以用 IEEE 754 单或双精度浮
点格式分别表示。
在类或接口的二进制表示中,constant_pool 表中剩下的结构还有
CONSTANT_NameAndType_info(§4.4.6)和 CONSTANT_Utf8_info(§4.4.7),它们被
间接用以获得对类、接口、方法、字段、方法类型和方法句柄的符号引用,或是在需要得到字符常
量和调用点限定符时被引用。
①
译者注:码点是指组成字符集代码空间的数值表示,譬如 ASCII 有 0x0 至 0x7F 共 128 个码点,扩展 ASCII
有 0x0 至 0xFF 共 256 个码点,而 Unicode 则有 0x0 至 0x10FFFF 共 1114112 个码点。
第 169 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
5.2 虚拟机启动
个初始类(Initial Class)来完成,这个类是由虚拟机的具体实现指定。紧接着,Java 虚拟
链接另外的一些类或接口,也可能会调用另外的方法。
实现也可以利用一个初始类让类加载器依次加载整个应用。初始类当然也可以选择组合上述的方式
来工作。
5.3 创建和加载
些方法而触发,譬如使用反射等。
如果 C 不是数组类型,那么它就可以通过类加载器加载 C 的二进制表示来创建(参见第 4 章,
通过类加载器加载的。
来获取类的二进制表示并创建类。例如,用户自定义类加载器可以通过网络下载、动态产生或是从
一个加密文件中提取类的信息。
当一个类加载器把加载请求委托给其它的类加载器后,发出这个加载请求的加载器与最终完成
第 170 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
(Initiating Loader)。
在 Java 虚拟机运行时,类或接口不仅仅是由它的名称来确定,而是由一个值对:二进制名称
(§4.2.1)和它的定义类加载器共同确定。每个这样的类或接口都归属于独立的运行时包结构
(Runtime Package)。类或接口的运行时包结构由包名及类或接口的定义类加载器来决定。
如果 N 表示一个非数组的类或接口,可以用下面的两个方法之一来加载并创建 C:
如果 D 是由引导类加载器所定义,那么引导类加载器初始加载 C(§5.3.1)。
如果 D 是由用户自定义类加载器所定义,那么此用户自定义类加载器也用来初始加
载 C(§5.3.2)。
组类 C 的过程中,D 的定义类加载器也要被用到。
当是当前正在(直接或间接)加载类或接口的那段程序中。
(这里有个需要注意的地方,作为解析(§5.3.5)过程的一部分,类加载器会递归加载它的
装成 NoClassDefFoundError 异常。)
请注意:一个功能良好的类加载器应当保证下面三个属性:
如果某个用户自定义的类加载器预先加载了某个类或接口的二进制表示,或是加载一组相
关的类型,并在加载时出现异常,那它必须在程序的某个点反映出加载时的错误。
第 171 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
Li
或接口的定义类加载器。我们也可以使用标识 N 来表示一个类或接口,这里的 N 表示类或接口
的名称,Li 表示类或接口的初始类加载器。
5.3.1 使用引导类加载器来加载类型
下列步骤描述使用引导类加载器加载并创建标记为 N 的非数组类型的类或接口 C。
如果是的话,这个类或接口就是 C,并且不再创建其它类型。
述。典型的情况是,类或文件会被表示为树型文件系统中的某个文件,类或接口的名称就是此文件
的路径名。
此处需要注意,搜索过程没有任何保证一定可以找到 C 的有效描述。所以加载的过程必须检
查到这些错误:
加载完成的类就是 C。
5.3.2 使用用户自定义类加载器来加载类型
类型的类或接口 C。
话,那个类或接口就是 C,不用再创建其它类了。
①
自从 JDK 版本 1.1 开始,Oracle 的 Java 虚拟机实现是通过调用类加载器的 loadClass 方法来加载类
或接口。方法 loadClass 的参数就是类或接口的名称。同时也存在着另外一个有两个参数的 loadClass 方法,
第 172 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
述这个过程。
两种操作之一来加载 C:
5.3.3 创建数组类
类加载器,也可以是用户自定义的类加载器。
1. 如果组件类型是引用类型,那就遵循这节(§5.3)的算法使用 L 递归加载和创建 C 的组
件类型。
2. Java 虚拟机使用显式的组件类型和数组维度来创建新的数组类。如果组件类型是引用类
组的组件类型是引用类型,数组类的可见性就由组件类型的可见性决定,否则,数组类的可见性将
被默认为 public。
5.3.4 加载限制
类加载器需要特别考虑到类型的安全链接问题。一种可能出现的情况是,当两个不同的类加载
这个符号引用会包含字段的特定描述符,或方法的参数和返回值类型。很重要的一点是,不管是
L1 还是 L2 加载,任何字段或方法描述符类型 N 都应该表示相同的类或接口。
L1 L2
为了确保这个原则,Java 虚拟机在准备(§5.4.2)和解析(§5.4.3)阶段强制“ N =N ”
载的某些关键点(§5.3.1、§5.3.2、§5.3.3、§5.3.4、§5.3.5)记录下每个特定类的初
始加载器。在记录一个加载器是某个类的初始加载器后,Java 虚拟机会立即检查是否有加载过程
违反了这一约束。如果有违约情况发生,此次记录过程将被撤销,Java 虚拟机抛出
LinkageError,引起记录产生的那次加载操作也同样会失败。
与之相似的是,在强制执行了加载约束(参见§5.4.2、§5.4.3.2、§5.4.3.3、
§5.4.3.4)之后,虚拟机必须立即去检查是否有违约情况发生。如果有,最新的那个加载约束
那些操作也会失败。
面的四个条件同时符合:
L L'
强加的限制定义了等价关系 N = N
C ≠ C’
对于类加载器和类型安全的进一步讨论已经超出了我们这篇规范的范畴。如果读者想了解更多
议记要)。
第 174 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
口的 Class 对象。
这个阶段的加载动作必须能够检测出下列错误:
抛出 ClassFormatError 异常。
否则,如果二进制表示的版本号不在虚拟机所支持的最低版本与最高版本之间(§4.1)
,
加载动作会抛出 UnspportedClassVersionError①异常。
或是它的子类异常。
类或接口解析过程中的异常可以被当作是加载阶段的异常抛出。除此之外,加载阶段还必须可
以检查出下列错误:
如果类或接口 C 的直接父类事实上是一个接口,那么加载过程就必须抛出
IncompatibleClassChangeError 异常。
述的算法来解析。
在类或接口解析过程中产生的异常可以当作是加载阶段的异常抛出。除此之外,加载过程还必
①
UnsupportedClassVersionError,它是 ClassFormatError 的子类,它可以很容易地从
ClassFormatError 异常中辨别出具体异常原因是由于 Java 虚拟机尝试加载的二进制表述使用了不支持的
Class 版本号。在 JDK 1.1 版本之前,如果发生不支持的版本问题,就会有 NoClassDefFoundError 或
ClassFormatError 被抛出,且取决于类是由引导类或用户自定义类加载器所加载。
第 175 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
须可以检查到下列错误:
如果类或接口 C 的直接父接口实际上不是一个接口的话,那么加载过程就必须抛出
IncompatibleClassChangeError 异常。
异常。
5.4 链接
链接类或接口包括验证和准备类或接口、它的直接父类、它的直接父接口、它的元素类型(如
果是一个数组类型)及其它必要的动作。而解析这个类或接口中的符号引用是链接过程中可选的部
分。
《Java 虚拟机规范》允许灵活地选择链接(并且会有递归加载)发生的时机,但必须保证下
列几点成立:
在类或接口被链接之前,它必须是被成功地加载过。
在类或接口初始化之前,它必须是被成功地验证及准备过.
程序的直接或间接行为可能会导致链接发生,链接过程中检查到的错误应该在请求链接的
程序处被抛出。
例如,Java 虚拟机实现可以选择只有在使用类或接口中符号引用时才去逐一解析它(延迟解
析),或是当类在验证时就解析每个引用(预先解析)。这意味着在一些虚拟机实现中,在类或接口
被初始化动作开始后,解析动作可能还正在进行。不管使用哪种策略,解析过程中的任何错误都必
须被抛出,抛出的位置是在通过直接或间接调用而导致解析过程发生的程序处。
由于链接过程会涉及到新数据结构的内存分配,它也可能因为发生 OutOfMemoryError 异
常而导致失败。
5.4.1 验证
验证(Verification,§4.10)阶段用于确保类或接口的二进制表示结构上是正确的
第 176 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
(§4.9)
。验证过程可能会导致某些额外的类和接口被加载进来(§5.3),但不应该会导致它们
也需要验证或准备。
败,那么随后对于此类或接口的验证尝试总是会因为第一次尝试失败的同样原因而失败。
5.4.2 准备
准备(Preparation)阶段的任务是为类或接口的静态字段分配空间,并用默认值初始化这
些字段(§2.3,§2.4)
。这个阶段不会执行任何的虚拟机字节码指令。在初始化阶段(§5.5)
会有显式的初始化器来初始化这些静态字段,所以准备阶段不做这些事情。
口<D,L2>中的某个方法,Java 虚拟机强制执行下面的加载约束:
给定 m 的返回值类型是 Tr,
(§2.4)。
类型(§2.4)。
L1 L2
那么对于 i = 0„n, Ti = Ti 就应该成立(§5.3.4)
。
L2 L3
0„n, Ti = Ti 就应该成立(§5.3.4)。
第 177 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
在类的对象创建之后的任何时间,都可以进行准备阶段,但它得确保一定要在初始化阶段开始
前完成。
5.4.3 解析
nvokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、
池。执行上述任何一条指令都需要对它的符号引用的进行解析。
解析(Resolution)是根据运行时常量池的符号引用来动态决定具体的值的过程。
invokedynamic 指令,相同的符号引用也被解析过。
但是对于上述的其它指令,当碰到某个因被其他指令引用而解析的符号引用时,就表示对于其
虚拟机在解析过程中也可以尝试去重新解析之前已经成功解析过的符号引用。如果有这样的尝
试动作,那么它总是会像之前一样解析成功,且总是返回此与引用初次解析时的结果相同的实体。
如果在某个符号引用解析过程中有错误发生,那么就应该在使用(直接或间接)这个符号引用
的每次试图对此引用的解析也总会抛出与第一次解析时相同的错误。
指令被实际执行之前不能被提早解析。
重新执行。
上述的某些指令在解析符号引用时,需要有额外的链接检查。譬如,getfield 指令为了成
就必须正常执行完成并返回一个合适的调用点对象。如果引导方法执行被中断或返回一个不合法的
调用点对象,那也必须抛出链接时异常。
致的,但这类问题依然会被当作是解析失败的问题。
后续各章节将描述对于类或接口 D 的运行时常量池(§5.1)中的符号引用进行解析的过程。
解析细节会因为符号引用类型的不同而有所区别。
5.4.3.1 类与接口解析
口的过程中,如果有任何异常发生,可以被认为是类和接口解析失败而抛出。过程的细节在 5.3
节进行描述。
如果 C 是数组类并且它的元素类型是引用类型,那么表示元素类型的类或接口的符号引
最后,检查 C 的访问权限:
如果 C 对 D 是不可见的(§5.4.4)
,类或接口解析抛出 IllegalAccessError 异常。
改成非 public。
5.4.3.2 字段解析
号引用应该首先被解析(§5.4.3.1)。因此,在解析类或接口引用时发生的任何异常都可以当作
第 179 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
是解析字段引用的异常一样被抛出。如果到 C 的引用可以被成功地解析,解析字段引用时发生的
异常就可以被抛出。
当解析一个字段引用时,字段解析会在 C 和它的父类中先尝试查找这个字段:
1. 如果 C 中声明的字段与字段引用有相同的名称及描述符,那么此次查找成功。字段查找的
结果是 C 中那个声明的字段。
2. 不然的话,字段查找就会被递归应用到类或接口 C 的直接父接口上。
4. 如果还不行,那么字段查找失败。
另外,假设<E,L1>是真正声明所引用字段的类或接口,L2 是 D 的定义加载器。给定引用字
L1 L2
Java 虚拟机必须强制执行这个的加载约束: T = T (§5.3.4)
。
5.4.3.3 普通方法解析
号引用应该首先被解析(§5.4.3.1)。因此,在解析类的符号引用时出现的任何异常都可以看作
解析方法引用的异常而被抛出。如果对 C 的引用成功地被解析,方法引用解析相关的异常就可以
被抛出。
当解析一个方法引用时:
1. 首先检查方法引用中的 C 是否类或接口。
2. 方法引用解析过程会检查 C 和它的父类中是否包含此方法:
如果 C 中确有一个方法与方法引用的指定名称相同,并且声明是签名多态方法
功的。所有方法描述符中所提到的类也需要解析(§5.4.3.1)。
这次解析的方法是签名多态方法。对于 C 来说,没有必要使用方法引用指定的描述符来
第 180 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
声明方法。
否则,如果 C 声明的方法与方法引用拥有同样的名称与描述符,那么方法查找也是成功。
3. 另外,方法查找过程也会试图从 C 的父接口中去定位所引用的方法。
如果 C 的某个父接口中确实声明了与方法引用相同的名称与描述符的方法,那么方法查
找成功。
否则,方法查找失败。
L1 L2
0„n,加载约束 Ti = Ti 能够成立(§5.3.4)。
5.4.3.4 接口方法解析
该最先被解析(§5.4.3.1)。因此,在解析接口引用时出现的任何异常都可以当作接口方法引用
解析的异常而被抛出。如果对接口引用被成功地解析,接口方法引用解析相关的异常就可以被抛出。
当解析接口方法引用时:
如果 C 不是一个接口,那么接口方法解析就会抛出 IncompatibleClassChangeError
异常。
第 181 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
L1 L2
拟机就要强制执行这样的约束: Ti = Ti (§5.3.4)
。
5.4.3.5 方法类型与方法句柄解析
为了解析方法类型(Method Type)的未解析符号引用,此方法类型所封装的方法描述符中
因此,在解析这些类型符号引用的过程中如果有任何异常发生,也会当作解析方法类型的异常而被
抛出。
来表示一个方法的描述符。
Sequence),它由方法句柄的类型(Kind)值来标识。九种方法句柄的类型值和描述见下表所示。
指令序列到字段或方法的符号引用被标记为:C.x:T。这里的 x 和 T 分别表示字段和方法的
名称和描述符(§4.3.2、§4.3.3),C 表示字段或方法所属的类或接口。
类型 描述 字节码行为
invokespecial C.<init>:(A*)void
第 182 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
假设 MH 表示正在被解析的方法句柄(§5.1)的符号引用,那么:
设 R 是 MH 中字段或方法的符号引用。
指向的 CONSTANT_Fieldref,CONSTANT_Methodref 或
设 C 是 R 所引用的类型的符号引用。
(C 是由表示 R 的 CONSTANT_Fieldref,CONSTANT_Methodref 和
得到。)
设 f 或 m 是 R 所引用的字段或方法的名称。
(f 或 m 是由 R 的 CONSTANT_Fieldref,CONSTANT_Methodref 和
CONSTANT_NameAndType 结构得到。)
设 T 和(如果是方法的话)A*是 R 所引用的字段或方法的返回值和参数类型序列
(T 和 A*是由得到 R 的 CONSTANT_Fieldref,CONSTANT_Methodref 和
CONSTANT_NameAndType 结构得到。)
(§5.4.3.1、§5.4.3.2、§5.4.3.3、§5.4.3.4)。那就是说,C、f、m、T 和 A*都需要
解析。在解析这些与类、字段或方法相关的符号引用过程中所抛出的异常,都可以看作是解析方法
句柄的异常而抛出。
方法请求成为合法的请求)
引用,就像下表中所述的给定 MH 的类型而去解析方法描述符的行为一样
类型 描述 方法描述符
1 REF_getField (C)T
第 183 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
2 REF_getStatic ()T
3 REF_putField (C,T)V
4 REF_putStatic (T)V
5 REF_invokeVirtual (C,A*)T
6 REF_invokeStatic (A*)T
7 REF_invokeSpecial (C,A*)T
8 REF_newInvokeSpecial (A*)C
9 REF_invokeInterface (C,A*)T
否则,o 是固定元方法句柄。
表示创建可变元方法句柄失败)。
java.lang.invoke.MethodType 的实例,它是之前由方法类型解析时产生的。
(在方法句柄的类型描述符上调用 java.lang.invoke.MethodHandle.invokeExact
方法会与字符码行一样,对栈造成相同的影响。当方法句柄带有有效的参数集合时,它会与相应的
字节码行为有相同的影响及返回值)
(Java 虚拟机的实现完全可以不需要内部的方法类型与方法句柄。那就是说,纵使拥有同样
结构的对方法类型或方法句柄的两个不同符号引用都可能会获得不同的
java.lang.invoke.MethodHandles 类创建方法句柄。具体的行为是由创建
java.lang.invoke.MethodHandles 的方法来决定。例如,当调用一个方法句柄时,就对传
入它的参数,并通过传入的值去调用另外一个方法句柄,接着将调用的返回值传回来,并返回传回
的值作为它的最终结果。
)
第 184 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
5.4.3.6 调用点限定符解析
调用点限定符提供对方法句柄的符号引用,它作为引导方法(Bootstrap Method)向
动态调用点提供服务。解析这个方法句柄(§5.4.3.5)是为了获取一个对
java.lang.invoke.MethodHandle 实例的引用。
调用点限定符提供了一个方法描述符,记作 TD。它是一个
返回值的方法类型(§5.4.3.5)的符号引用而获得。
调用点限定符提供零至多个静态参数(Static Arguments),用于传递与特定应用相
关的元数据给引导方法。静态参数只要是对类、方法句柄或方法类型的符号引用,都需要
调用点限定符的解析结果是一个数组,它包含:
到 java.lang.invoke.MethodHandle 实例的引用,
到 java.lang..invoke.MethodType 实例的引用,
到 Class,java.lang.invoke.MethodHandle,
在解析调用点限定符的方法句柄符号引用,或解析调用点限定符中方法类型的描述符的符号引
用时,或是解析任何的静态参数的符号引用时,任何与方法类型或方法句柄解析(§5.4.3.5)
有关的异常都可以被抛出。
5.4.3 访问控制
成立:
第 185 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
C 是 public 的。
C 和 D 处于同一个运行包(Runtime Package,§5.3)下面。
R 是 public 的。
要么就是 D 的子类或父类。
不是 private,并且它所属的那个类应该与 D 处于同一运行包下)。
目标(调用的目标必须是 D 或它的子类型)。这需要在验证部分(§5.4.1)做相应地检查。它不
是链接过程中的访问控制所需要涉及的。
5.4.5 方法覆盖
当且仅当下面的条件都成立才合法:
C 是 A 的子类。
m2 与 m1 拥有相同的名称及方法描述符。
下面其中一条成立:
一运行包下面)。
第 186 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
5.5 初始化
初始化(Initialization)对于类或接口来说,就是执行它的初始化方法(§2.9)。在发
生下列行为时,类或接口将会被初始化:
或 invokestatic。这些指令通过字段或方法引用来直接或间接地引用其它类。执行上
没有被初始化那就初始化它。
虚拟机解析出类型是 2(REF_getStatic)、4(REF_putStatic)或者 6
(REF_invokeStatic)的方法句柄(§5.4.3.5)。
在对于类的某个子类的初始化时。
在类或接口被初始化之前,它必须被链接过,也就是经过验证、准备阶段,且有可能已经被解
析完成了。
因为 Java 虚拟机是支持多线程的,所以在初始化类或接口的时候要特别注意线程同步问题,
可能其它一些线程也想要初始化相同名称的类或接口。也有可能在初始化一些类或接口时,初始的
请求被递归要求初始化它自己。Java 虚拟机实现需要负责处理好线程同步和递归初始化,具体可
述的四种状态之一:
Class 对象已经被验证和准备过,但还没有被初始化。
Class 对象正在被其它特定线程初始化。
Class 对象已经成功被初始化且可以使用。
Class 对象处于错误的状态,可能因为尝试初始化时失败过
初始化 C 的过程如下:
第 187 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
同步 C 的初始化锁 LC。这个操作会导致当前线程一直等待直到可以获得 LC 锁。
LC 并进入阻塞状态,直到它知道初始化工作已经由其它线程完成,那么当前线程在此重试此步骤。
释放 LC 并正常返回。
返回。
NoClassDefFoundError 异常。
执行 C 的类或接口初始化方法。
已经被完全初始化,通知所有正在等待的线程,接着释放 LC,正常地退出整个过程。
E 的参数。
上述的具体错误对象作为此次意外中断的原因。
虚拟机在具体实现时可以通过省略第 1 步在检查类初始化是否完成时的锁获取过程(在第 4、
第 188 页 / 共 387 页
Java 虚拟机规范 — 第 5 章 加载、链接与初始化
规则(JLS §17.4.5)在锁释放后依然存在,因此可以进行相应的优化。
5.6 绑定本地方法实现
统编译原理的表述中被称“链接”,所以规范里使用“绑定”这个词就,就是为了避免与 Java 虚
拟机中链接类或接口的语义发生冲突。
第 189 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
第 6 章 Java 虚拟机指令集
一条 Java 虚拟机指令由一个特定操作的操作码和零至多个操作所使用到的操作数所构成。在
6.1 设定:“必须”的含义
我们无法完全孤立地介绍字节码指令作用,对指令的描述应在给定的符合“第 4 章 Class 文
在第 4 章的约束下,所有被“必须”或“不允许”修饰的要求都需要得到满足。如果在某些运行
规范》不会去定义违反约束前提下的虚拟机行为。
过程只会执行一次。当然,其他实现策略也是可以的,只要保证这些实现遵循《Java 语言规范》
和《Java 虚拟机规范》即可。
6.2 保留操作码
将来被扩充的话,这 3 个保留操作码需要保证不被占用。
的两个操作码是作为“后门”和“陷阱”出现,目的是在某些硬件和软件中提供一些与实现相关的
断点功能。
第 190 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
Class 文件之中。调试器或者即时代码生成器(§2.13)可以直接与已经加载的或者正在执行中
的 Java 虚拟机代码交互,如果遇到这些保留操作码,那调试器或代码生成器应当能对其语义做出
正确的处理。
6.3 虚拟机错误
法预测虚拟机会遇到哪些内部错误或者资源受限的情况,也不可能精确地指出虚拟机何时会发生这
拟机运作过程中的任意时刻:
的出现,InternalError 是一个典型的异步异常(§2.10)
,它可能出现在程序中的任
何位置。
管理子系统无法回收到足够共新对象分配所需的内存空间时,虚拟机将抛出
OutOfMemoryError 异常。
是由于程序执行时无限制的递归调用而导致的,
虚拟机将会抛出 StackOverflowError
异常。
UnknownError:当某种异常或错误出现,但虚拟机实现无法确定具体实际是哪种异常
6.4 指令描述格式
来表示。每一条指令都将采用一页独立页面进行描述。
第 191 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
Java 虚拟机代码只会出现数字形式的操作码,不会出现助记符。
操作数会在运行期参与运算并加载到操作数栈当中。虽然操作数可能会有不同的来源,但是它们都
有部分指令会以一系列描述、格式、操作数栈图等都一致的关联指令族的形式出现,这种指令
族会包含若干个操作码和操作码助记符,但只有指令族的助记符会出现在指令格式表中,并且在“结
构”行中会列出指令族所包括的所有助记符和操作码。例如 lconst_<l>指令族的“结构”行就
结构 lconst_0 = 9(0x9)
lconst_1 = 10(0xa)
在 Java 虚拟机指令描述中,指令执行之后对当前栈帧(§2.6)的操作数栈(§2.6.2)产
生的影响将会使用文本的方式从左至右分别显示操作数栈中每一个数值的变化,因此,下面“操作
操作数栈 …,value1,value2 →
…,result
操作数栈中的其余部分使用一组省略号(„)来表示,代表指令执行不会影响到操作数栈的这
部分的内容。
①
在本规范的第一版中,操作数栈内的 long 核 double 数据使用了两个栈元素来表示。
第 192 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
表 6.1 指令格式表
助记符
操作 该指令功能的简要描述
格式 助记符
操作数 1
操作数 2
„„
结构 助记符 = 操作码
操作数栈 „,value1,value2 →
„,result
描述 关于操作数栈内容、常量池项、指令操作和结果类型等信息的详细描述。
链接时异常 如果执行该指令可能抛出任何链接时异常,那么每一个可能抛出的异常都需要
在此进行描述。
运行时异常 如果执行该指令可能抛出任何运行时异常,那么每一个可能抛出的异常都需要
在此进行描述。
类之外,指令不得再抛出其他任何异常。
注意 某些并非本规范对该指令强制约束的注释,将会在这里描述。
6.5 指令集描述
第 193 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
aaload
格式 aaload
结构 aaload = 50(0x32)
操作数栈 „,arrayref,index →
„,value
类型值将压入到操作数栈中。
常。
第 194 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
aastore
格式 aastore
结构 aastore = 83(0x53)
操作数栈 „,arrayref,index,value →
reference(记作 T)的数组的前提是:
如果 S 是类类型(Class Type),那么:
代表的类型的子类。
如果 S 是接口类型(Interface Type),那么:
如果 T 是接口类型,那么 T 与 S 应当是相同的接口,或者 T 是 S 的
父接口。
件类型为 SC,那么:
第 195 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
那么下面两条规则之一必须成立:
TC 和 SC 是同一个原始类型。
(以此处描述的规则来判断是否互相匹配)。
如果 T 是接口类型,那 T 必须是数组类型所实现的接口之一(JLS3
§4.10.3)
。
异常
ArrayStoreException 异常。
第 196 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
aconst_null
格式 aconst_null
结构 aconst_null = 1(0x1)
操作数栈 „ →
„,null
际表示的。
第 197 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
aload
格式 aload
index
结构 aload = 25(0x19)
操作数栈 „ →
„,objectref
objectref。指令执行后,objectref 将会压入到操作数栈栈顶。
returnAddress 类型的数据)。
byte 型数值作为索引来访问局部变量表。
第 198 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
aload_<n>
格式 aload_<n>
结构 aload_0 = 42(0x2a)
aload_1 = 43(0x2b)
aload_2 = 44(0x2c)
aload_3 = 45(0x2d)
操作数栈 „ →
„,objectref
描述 <n>代表当前栈帧(§2.6)中局部变量表的索引值,<n>作为索引定位的局部
将会压入到操作数栈栈顶
数栈中,这点是特意设计成与 astore_<n>指令不相对称的(astore_<n>
令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 199 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
anewarray
格式 anewarray
indexbyte1
indexbyte2
结构 anewarray = 189(0xbd)
操作数栈 „,count →
„,arrayref
indexbyte2,该索引所指向的运行时常量池项应当是一个类、接口或者数组
类型的符号引用,这个类、接口或者数组类型应当是已被解析(§5.4.3.1)
的默认值。
都可能被抛出。
NegativeArraySizeException 异常。
注意 anewarray 指令可用于创建一个单维度的数组,或者用于创建一个多维度数
组的一部分。
第 200 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
areturn
格式 areturn
结构 areturn = 176(0xb0)
操作数栈 „,objectref →
[empty]
当前方法的方法描述符(§4.3.3)中的返回值相匹配(JLS §5.2)的对象。
如果当前方法是一个同步(声明为 synchronized)方法,那在方法调用时
进入或者重入的管程应当被正确更新状态或退出,就像当前线程执行了
monitorexit 指令一样。如果执行过程当中没有异常被抛出的话,那
objectref 将从当前栈帧(§2.6)中出栈,并压入到调用者栈帧的操作数
栈中,在当前栈帧操作数栈中所有其他的值都将会被丢弃掉。
指令执行后,解释器会恢复调用者的栈帧,并且把程序控制权交回到调用者。
当前方法是一个同步方法,但当前线程在调用方法时没有成功持有(Enter)
IllegalMonitorStateException 异常。这是可能出现的,譬如一个同步
monitorenter 指令。
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,areturn 指令也会抛出
IllegalMonitorStateException 异常。
第 201 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
arraylength
操作 取数组长度
格式 arraylength
结构 arraylength = 190(0xbe)
操作数栈 „,arrayref →
„,length
int 类型数据压入到操作数栈中。
NullPointerException 异常。
第 202 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
astore
格式 astore
index
结构 astore = 58(0x3a)
操作数栈 „,objectref →
finally 子句(参见§3.13,
“编译 fianlly”)
。但是 aload 指令不可以用
指令的不对称性是有意设计的。
数作为索引来访问局部变量表。
第 203 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
astore_<n>
格式 astore_<n>
结构 astore_0 = 75(0x4b)
astore_1 = 76(0x4c)
astore_2 = 77(0x4d)
astore_3 = 78(0x4e)
操作数栈 „,objectref →
描述 <n>必须是一个指向当前栈帧(§2.6)局部变量表的索引值,而在操作数栈
这个数据将从操作数栈出栈,然后保存到<n>所指向的局部变量表位置中。
种 astore_<n>指令的不对称性是有意设计的。
指令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 204 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
athrow
操作 抛出一个异常实例(exception 或者 error)
格式 athrow
结构 athrow = 191(0xbf)
操作数栈 „,objectref →
objectref
或其子类的对象实例。在指令执行时,objectref 首先从操作数栈中出栈,
型相匹配的第一个异常处理器。
处理此异常的代码位置。PC 寄存器的值就会被重设为异常处理器指定的那个
位置上,整个当前栈帧的操作数栈都会被清空,objectref 重新压入到操作
数栈中,然后程序继续执行。
如果在当前栈帧中没有找到适合的异常处理器,那么栈帧就要从操作数栈中出
栈,如果当前栈帧对应的方法是一个同步方法,那在方法调用时持有或重入的
样。最后,这个栈帧的调用者被恢复。如果此栈帧仍然没有找到合适的异常处
理器,那它也会继续退出,objectref 也会不断重新抛出,假设已经没有任
何的栈帧可以退出,那当前线程将被结束掉。
另外,如果虚拟机实现没有严格执行在§2.11.10 中规定的结构化锁定规则,
导致当前方法是一个同步方法,但当前线程在调用方法时没有成功持有或重入
第 205 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
异常。这是可能出现的,譬如一个同步方法只包含了对方法同步对象的
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,athrow 指令也会抛出
IllegalMonitorStateException 异常。
注意 athrow 指令的操作数栈图(本表中“操作数栈”行的图)可能会产生一些误
解:如果当前方法中某个异常处理器被匹配到,athrow 指令将抛弃掉操作数
栈上所有的值,然后重新将被抛出的异常对象入栈,但是如果在当前方法中没
有找到适合的异常处理器,即异常被抛到方法调用链其他地方时,被清空的和
objectref 入栈的操作数栈是真正处理异常的那个方法的操作数栈,而从最
初抛出异常的那个方法一直到最终处理异常的那个方法(不含)之间的栈帧全
部都会被丢弃掉。
第 206 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
baload
格式 baload
结构 baload = 51(0x33)
操作数栈 „,arrayref,index →
„,value
据并压入到操作数栈中。
常。
ArrayIndexOutOfBoundsException 异常。
的虚拟机实现中,布尔类型的数组(T_BOOLEAN 类型的数组可参见§2.2 和
就必须能正确访问相应实现的数组。
第 207 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
bastore
格式 bastore
结构 bastore = 84(0x54)
操作数栈 „,arrayref,index,value →
异常
bastore 就必须能正确访问相应实现的数组。
第 208 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
bipush
格式 bipush
byte
结构 bipush = 16(0x10)
操作数栈 „ →
„,value
数栈中。
第 209 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
caload
格式 caload
结构 caload = 52(0x34)
操作数栈 „,arrayref,index →
„,value
操作数栈中。
第 210 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
castore
格式 castore
结构 castore = 85(0x55)
操作数栈 „,arrayref,index,value →
index 作为索引定位到数组元素中。
异常
第 211 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
checkcast
操作 检查对象是否符合给定的类型
格式 checkcast
indexbyte1
Indexbyte2
结构 checkcast = 192(0xc0)
操作数栈 „,objectref →
„,objectref
用于构建一个当前类(§2.6)的运行时常量池的索引值,构建方式为
当是一个类、接口或者数组类型的符号引用。
否则,参数指定的类、接口或者数组类型会被虚拟机解析(§5.4.3.1)。如
果 objectref 可以转换为这个类、接口或者数组类型,那操作数栈就保持不
类、接口或者数组类型,checkcast 指令根据这些规则来判断转换是否成立:
如果 S 是类类型(Class Type),那么:
代表的类型的子类。
如果 S 是接口类型(Interface Type),那么:
第 212 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
如果 T 是接口类型,那么 T 与 S 应当是相同的接口,或者 T 是 S 的
父接口。
件类型为 SC,那么:
那么下面两条规则之一必须成立:
TC 和 SC 是同一个原始类型。
(以此处描述的规则来判断是否互相匹配)。
都可能被抛出。
指令将抛出 ClassCastException 异常
instanceof 是返回一个比较结果)以及指令执行后对操作数栈的影响。
第 213 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
d2f
格式 d2f
结构 d2f = 144(0x90)
操作数栈 „,value →
„,result
从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,
转换为单精度浮点值集合中与原值最接近的可表示值。
指数集合(§2.3.2)中选取,也就是说并非一定会转换为单精度浮点值集合
中与原值最接近的可表示值的。
换为与原值符号相同的零值。同样,当有限值 value’太大以至于无法使用
float 类型数据来表示时,将会被转换为与原值符号相同的无穷大。double
§5.1.3),它可能会导致 value’的数值大小和精度发生丢失。
第 214 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
d2i
格式 d2i
结构 d2i = 142(0x8e)
操作数栈 „,value →
„,result
从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,
另外,如果 value’太小(绝对值很大的负数或者负无穷大)以至于超过
§5.1.3),它可能会导致 value’的数值大小和精度发生丢失。
第 215 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
d2l
格式 d2l
结构 d2l = 143(0x8f)
操作数栈 „,value →
„,result
从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,
另外,如果 value’太小(绝对值很大的负数或者负无穷大)以至于超过
§5.1.3),它可能会导致 value’的数值大小和精度发生丢失。
第 216 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dadd
操作 double 类型数据相加
格式 dadd
结构 dadd = 99(0x63)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
两个不同符号的无穷大相加,结果为 NaN。
两个相同符号的无穷大相加,结果仍然为相同符号的无穷大。
一个无穷大的数与一个有限的数相加,结果为无穷大。
两个不同符号的零值相加,结果为正零。
两个相同符号的零值相加,结果仍然为相同符号的零值。
零值与一个非零有限值相加,结果等于那个非零有限值。
两个绝对值相等、符号相反的非零有限值相加,结果为正零。
对于上述情况之外的场景,即任意一个操作数都不是无穷大、零、NaN 以
及两个值具有相同符号或者不同的绝对值,就按算术求和,并以最接近数
舍入模式得到运算结果。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 dadd
指令永远不会抛出任何运行时异常。
第 217 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
daload
格式 daload
结构 daload = 49(0x31)
操作数栈 „,arrayref,index →
„,value
到操作数栈中。
第 218 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dastore
格式 dastore
结构 dastore = 82(0x52)
操作数栈 „,arrayref,index,value →
定位到数组元素中。
异常
第 219 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dcmp<op>
操作 比较 2 个 double 类型数据的大小
格式 dcmp<op>
结构 dcmpg = 152(0x98)
dcmpl = 151(0x97)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
,接着对这 2 个值进行浮点比较操作:
value1’和 value2’
栈中。
作数栈中。
操作数栈中。
是有序的,正无穷大大于所有有限值,正数零和负数零则被看作是相等的。
导致比较失败,都会有一个确定的返回值压入到操作数栈中。读者可以参见§
第 220 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
3.5“更多控制例子”获取更多的信息。
第 221 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dconst_<d>
操作 将 double 类型数据压入到操作数栈中
格式 dconst_<d>
结构 dconst_0 = 14(0xe)
dconst_1 = 15(0xf)
操作数栈 „ →
„,<d>
第 222 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ddiv
操作 double 类型数据除法
格式 ddiv
结构 ddiv = 111(0x6f)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
value1’和 value2’,接着将这两个数值相除(value1’÷value2’)
,结
算结果为正,反着,当两者符号不同时,运算结果为负。
两个无穷大相除,运算结果为 NaN。
一个无穷大的数与一个有限的数相除,结果为无穷大,无穷大的符号由第
2 点规则确定。
一个有限的数与一个无穷大的数相除,结果为零,零值的符号由第 2 点规
则确定。
零除以零结果为 NaN,零除以任意其他非零有限值结果为零,零值的符号
由第 2 点规则确定。
任意非零有限值除以零结果为无穷大,无穷大的符号由第 2 点规则确定。
对于上述情况之外的场景,即任意一个操作数都不是无穷大、零以及 NaN,
第 223 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
说就是出现了上限溢出,那结果将会使用具有适当符号的无穷大来代替。
说就是出现了下限溢出,那结果将会使用具有适当符号的零值来代替。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 ddiv
指令永远不会抛出任何运行时异常。
第 224 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dload
格式 dload
index
结构 dload = 24(0x18)
操作数栈 „ →
„,value
和 index+1 两个位置)
,记为 value。指令执行后,value 将会压入到操作
数栈栈顶
byte 型数值作为索引来访问局部变量表。
第 225 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dload_<n>
格式 dload_<n>
结构 dload_0 = 38(0x26)
dload_1 = 39(0x27)
dload_2 = 40(0x28)
dload_3 = 41(0x29)
操作数栈 „ →
„,value
描述 <n>和<n>+1 共同代表一个当前栈帧(§2.6)中局部变量表的索引值,<n>
value 将会压入到操作数栈栈顶
令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 226 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dmul
操作 double 类型数据乘法
格式 dmul
结构 dmul = 107(0x6b)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
value1’和 value2’,接着将这两个数值相乘(value1’×value2’)
,结
算结果为正,反着,当两者符号不同时,运算结果为负。
无穷大与零值相乘,运算结果为 NaN。
一个无穷大的数与一个有限的数相乘,结果为无穷大,无穷大的符号由第
2 点规则确定。
对于上述情况之外的场景,即任意一个操作数都不是无穷大或者 NaN,就
就是出现了上限溢出,那结果将会使用具有适当符号的无穷大来代替。如
就是出现了下限溢出,那结果将会使用具有适当符号的零值来代替。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 dmul
第 227 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
指令永远不会抛出任何运行时异常。
第 228 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dneg
操作 double 类型数据取负运算
格式 dneg
结构 dneg = 119(0x77)
操作数栈 „,value →
„,result
并且经过数值集合转换(§2.8.3)后得到值 value’,接着对这个数进行算
作数栈中。
对于 double 类型数据,取负运算并不等同于与零做减法运算。如果 x 是+
算仅仅把数值的符号反转。
下面是一些值得注意的场景:
如果操作数是无穷大,那运算结果是与其符号相反的无穷大。
如果操作数是零,那运算结果是与其符号相反的零值。
第 229 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
drem
操作 double 类型数据求余
格式 drem
结构 drem = 115(0x73)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
尾触发(Truncating Division)来获得求余结果,因此这种运算与通常对
数互相比较。
drem 指令的运算结果通过以下规则获得:
的符号一致。
如果被除数是无穷大,或者除数为零,那运算结果为 NaN。
如果被除数是有限值,而除数是无穷大,那运算结果等于被除数。
如果被除数为零,而除数是有限值,那运算结果等于被除数。
对于上述情况之外的场景,即任意一个操作数都不是无穷大、零以及 NaN,
就以 value1’为被除数、value2’为除数使用浮点算术规则求余:
第 230 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
与 value1’÷value2’的符号相同,大小与他们的商相同。
上限溢出、下限溢出和进度丢失的情况也不会出现。
来完成。
第 231 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dreturn
格式 dreturn
结构 dreturn = 175(0xaf)
操作数栈 „,value →
[empty]
的数据。如果当前方法是一个同步(声明为 synchronized)方法,那在方
法调用时进入或者重入的管程应当被正确更新状态或退出,就像当前线程执行
了 monitorexit 指令一样。如果执行过程当中没有异常被抛出的话,那
value 将从当前栈帧(§2.6)中出栈,并且经过数值集合转换(§2.8.3)
后得到值 value’,然后压入到调用者栈帧的操作数栈中,在当前栈帧操作数
栈中所有其他的值都将会被丢弃掉。
指令执行后,解释器会恢复调用者的栈帧,并且把程序控制权交回到调用者。
当前方法是一个同步方法,但当前线程在调用方法时没有成功持有(Enter)
IllegalMonitorStateException 异常。这是可能出现的,譬如一个同步
monitorenter 指令。
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,dreturn 指令也会抛出
IllegalMonitorStateException 异常。
第 232 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dstore
格式 dstore
index
结构 dstore = 57(0x39)
操作数栈 „,value →
的数据,这个数据将从操作数栈出栈,并且经过数值集合转换(§2.8.3)后
中。
数作为索引来访问局部变量表。
第 233 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dstore_<n>
格式 dstore_<n>
结构 dstore_0 = 71(0x47)
dstore_1 = 72(0x48)
dstore_2 = 73(0x49)
dstore_3 = 74(0x4a)
操作数栈 „,value →
描述 <n>和<n>+1 必须是一个指向当前栈帧(§2.6)局部变量表的索引值,而在
出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,然后保存到<n>
和<n>+1 所指向的局部变量表位置中。
指令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 234 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dsub
操作 double 类型数据相减
格式 dsub
结构 dsub = 103(0x67)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
,接着将这两个数值相减(result = value1’−
value1’和 value2’
作数栈中。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 dsub
指令永远不会抛出任何运行时异常。
第 235 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dup
操作 复制操作数栈栈顶的值,并插入到栈顶
格式 dup
结构 dup = 89(0x59)
操作数栈 „,value →
„,value,value
描述 复制操作数栈栈顶的值,并将此值压入到操作数栈顶。
使用 dup 指令来复制栈顶值。
第 236 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dup_x1
操作 复制操作数栈栈顶的值,并插入到栈顶以下 2 个值之后
格式 dup_x1
结构 dup_x1 = 90(0x5a)
操作数栈 „,value2,value1 →
„,value1,value2,value1
描述 复制操作数栈栈顶的值,并将此值压入到操作数栈顶以下 2 个值之后。
第 237 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dup_x2
操作 复制操作数栈栈顶的值,并插入到栈顶以下 2 个或 3 个值之后
格式 dup_x2
结构 dup_x2 = 91(0x5b)
操作数栈 结构 1:
„,value3,value2,value1 →
„,value1,value3,value2,value1
的数据类型时满足结构 1。
结构 2:
„,value2,value1 →
„,value1,value2,value1
是分类二的数据类型时满足结构 2。
描述 复制操作数栈栈顶的值,并将此值压入到操作数栈顶以下 2 个或 3 个值之后。
第 238 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dup2
操作 复制操作数栈栈顶 1 个或 2 个值,并插入到栈顶
格式 dup2
结构 dup2 = 92(0x5c)
操作数栈 结构 1:
„,value2,value1 →
„,value2,value1,value2,value1
时满足结构 1。
结构 2:
„,value →
„,value,value
描述 复制操作数栈栈顶 1 个或 2 个值,并将这些值按照原来的顺序压入到操作数栈
顶。
第 239 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dup2_x1
格式 dup2_x1
结构 dup2_x1 = 93(0x5d)
操作数栈 结构 1:
„,value3,value2,value1 →
„,value2,value1,value3,value2,value1
的数据类型时满足结构 1。
结构 2:
„,value2,value1 →
„,value1,value2,value1
是分类一的数据类型时满足结构 2。
描述 复制操作数栈栈顶 1 个或 2 个值,并按照原有的顺序插入到栈顶以下 2 个或 3
个值之后。
第 240 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
dup2_x2
格式 dup2_x2
结构 dup2_x2 = 94(0x5e)
操作数栈 结构 1:
„,value4,value3,value2,value1 →
„,value2,value1,value4,value3,value2,value1
列出的分类一中的数据类型时满足结构 1。
结构 2:
„,value3,value2,value1 →
„,value1,value3,value2,value1
和 value3 是分类一的数据类型时满足结构 2。
结构 3:
„,value3,value2,value1 →
„,value2,value1,value3,value2,value1
而 value3 是分类二的数据类型时满足结构 3。
结构 4:
„,value2,value1 →
„,value1,value2,value1
满足结构 4。
第 241 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
个或者 3 个值之后。
第 242 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
f2d
格式 f2d
结构 f2d = 141(0x8d)
操作数栈 „,value →
„,result
从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,
因为所有单精度浮点数集合(§2.3.2)都可以在双精度浮点数集合(§
2.3.2)中找到精确对应的数值,因此这种转换是精确的。
展指数集合(§2.3.2)中选取,并不需要舍入为双精度浮点数集合中最接近
把结果舍入为双精度浮点数集合则是必须的。
第 243 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
f2i
格式 f2i
结构 f2i = 139(0x8b)
操作数栈 „,value →
„,result
从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,
另外,如果 value’太小(绝对值很大的负数或者负无穷大)以至于超过
§5.1.3),它可能会导致 value’的数值大小和精度发生丢失。
第 244 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
f2l
格式 f2l
结构 f2l = 140(0x8c)
操作数栈 „,value →
„,result
从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,
另外,如果 value’太小(绝对值很大的负数或者负无穷大)以至于超过
§5.1.3),它可能会导致 value’的数值大小和精度发生丢失。
第 245 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fadd
操作 float 类型数据相加
格式 fadd
结构 dadd = 99(0x63)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
两个不同符号的无穷大相加,结果为 NaN。
两个相同符号的无穷大相加,结果仍然为相同符号的无穷大。
一个无穷大的数与一个有限的数相加,结果为无穷大。
两个不同符号的零值相加,结果为正零。
两个相同符号的零值相加,结果仍然为相同符号的零值。
零值与一个非零有限值相加,结果等于那个非零有限值。
两个绝对值相等、符号相反的非零有限值相加,结果为正零。
对于上述情况之外的场景,即任意一个操作数都不是无穷大、零、NaN 以
及两个值具有相同符号或者不同的绝对值,就按算术求和,并以最接近数
舍入模式得到运算结果。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 fadd
指令永远不会抛出任何运行时异常。
第 246 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
faload
格式 faload
结构 faload = 48(0x30)
操作数栈 „,arrayref,index →
„,value
到操作数栈中。
第 247 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fastore
格式 fastore
结构 fastore = 81(0x51)
操作数栈 „,arrayref,index,value →
定位到数组元素中。
异常
第 248 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fcmp<op>
操作 比较 2 个 float 类型数据的大小
格式 fcmp<op>
结构 fcmpg = 150(0x96)
fcmpl = 149(0x95)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
,接着对这 2 个值进行浮点比较操作:
value1’和 value2’
栈中。
作数栈中。
操作数栈中。
是有序的,正无穷大大于所有有限值,正数零和负数零则被看作是相等的。
导致比较失败,都会有一个确定的返回值压入到操作数栈中。读者可以参见§
第 249 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
3.5“更多控制例子”获取更多的信息。
第 250 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fconst_<f>
操作 将 float 类型数据压入到操作数栈中
格式 fconst_<f>
结构 fconst_0 = 11(0xb)
fconst_1 = 12(0xc)
fconst_2 = 13(0xd)
操作数栈 „ →
„,<f>
第 251 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fdiv
操作 float 类型数据除法
格式 fdiv
结构 fdiv = 110(0x6e)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
value1’和 value2’,接着将这两个数值相除(value1’÷value2’)
,结
算结果为正,反着,当两者符号不同时,运算结果为负。
两个无穷大相除,运算结果为 NaN。
一个无穷大的数与一个有限的数相除,结果为无穷大,无穷大的符号由第
2 点规则确定。
一个有限的数与一个无穷大的数相除,结果为零,零值的符号由第 2 点规
则确定。
零除以零结果为 NaN,零除以任意其他非零有限值结果为零,零值的符号
由第 2 点规则确定。
任意非零有限值除以零结果为无穷大,无穷大的符号由第 2 点规则确定。
对于上述情况之外的场景,即任意一个操作数都不是无穷大、零以及 NaN,
第 252 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
说就是出现了上限溢出,那结果将会使用具有适当符号的无穷大来代替。
说就是出现了下限溢出,那结果将会使用具有适当符号的零值来代替。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 fdiv
指令永远不会抛出任何运行时异常。
第 253 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fload
格式 fload
index
结构 fload = 23(0x17)
操作数栈 „ →
„,value
指令执行后,value 将会压入到操作数栈栈顶
byte 型数值作为索引来访问局部变量表。
第 254 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fload_<n>
格式 fload_<n>
结构 fload_0 = 34(0x22)
fload_1 = 35(0x23)
fload_2 = 36(0x24)
fload_3 = 37(0x25)
操作数栈 „ →
„,value
描述 <n>代表一个当前栈帧(§2.6)中局部变量表的索引值,<n>作为索引定位的
操作数栈栈顶
令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 255 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fmul
操作 float 类型数据乘法
格式 fmul
结构 fmul = 106(0x6a)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
value1’和 value2’,接着将这两个数值相乘(value1’×value2’)
,结
算结果为正,反着,当两者符号不同时,运算结果为负。
无穷大与零值相乘,运算结果为 NaN。
一个无穷大的数与一个有限的数相乘,结果为无穷大,无穷大的符号由第
2 点规则确定。
对于上述情况之外的场景,即任意一个操作数都不是无穷大或者 NaN,就
就是出现了上限溢出,那结果将会使用具有适当符号的无穷大来代替。如
就是出现了下限溢出,那结果将会使用具有适当符号的零值来代替。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 fmul
第 256 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
指令永远不会抛出任何运行时异常。
第 257 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fneg
操作 float 类型数据取负运算
格式 fneg
结构 fneg = 118(0x76)
操作数栈 „,value →
„,result
且经过数值集合转换(§2.8.3)后得到值 value’,接着对这个数进行算术
数栈中。
把数值的符号反转。
下面是一些值得注意的场景:
如果操作数是无穷大,那运算结果是与其符号相反的无穷大。
如果操作数是零,那运算结果是与其符号相反的零值。
第 258 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
frem
操作 float 类型数据求余
格式 frem
结构 frem = 114(0x72)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
尾触发(Truncating Division)来获得求余结果,因此这种运算与通常对
数互相比较。
drem 指令的运算结果通过以下规则获得:
的符号一致。
如果被除数是无穷大,或者除数为零,那运算结果为 NaN。
如果被除数是有限值,而除数是无穷大,那运算结果等于被除数。
如果被除数为零,而除数是有限值,那运算结果等于被除数。
对于上述情况之外的场景,即任意一个操作数都不是无穷大、零以及 NaN,
就以 value1’为被除数、value2’为除数使用浮点算术规则求余:
第 259 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
与 value1’÷value2’的符号相同,大小与他们的商相同。
上限溢出、下限溢出和进度丢失的情况也不会出现。
来完成。
第 260 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
freturn
格式 freturn
结构 freturn = 174(0xae)
操作数栈 „,value →
[empty]
据。如果当前方法是一个同步(声明为 synchronized)方法,那在方法调
用时进入或者重入的管程应当被正确更新状态或退出,就像当前线程执行了
将从当前栈帧(§2.6)中出栈,并且经过数值集合转换(§2.8.3)后得到
值 value’,然后压入到调用者栈帧的操作数栈中,在当前栈帧操作数栈中所
有其他的值都将会被丢弃掉。
指令执行后,解释器会恢复调用者的栈帧,并且把程序控制权交回到调用者。
当前方法是一个同步方法,但当前线程在调用方法时没有成功持有(Enter)
IllegalMonitorStateException 异常。这是可能出现的,譬如一个同步
monitorenter 指令。
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,freturn 指令也会抛出
IllegalMonitorStateException 异常。
第 261 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fstore
格式 fstore
index
结构 fstore = 56(0x38)
操作数栈 „,value →
从操作数栈出栈,并且经过数值集合转换(§2.8.3)后得到值 value’,然
数作为索引来访问局部变量表。
第 262 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fstore_<n>
格式 fstore_<n>
结构 fstore_0 = 67(0x43)
fstore_1 = 68(0x44)
fstore_2 = 69(0x45)
fstore_3 = 70(0x46)
操作数栈 „,value →
描述 <n>必须是一个指向当前栈帧(§2.6)局部变量表的索引值,而在操作数栈
经过数值集合转换(§2.8.3)后得到值 value’,然后保存到<n>所指向的
局部变量表位置中。
指令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 263 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
fsub
操作 float 类型数据相减
格式 fsub
结构 fsub = 102(0x66)
操作数栈 „,value1,value2 →
„,result
value2 从操作数栈中出栈,并且经过数值集合转换(§2.8.3)后得到值
,接着将这两个数值相减(result = value1’−
value1’和 value2’
数栈中。
尽管指令执行期间,上溢、下溢以及精度丢失等情况都有可能发生,但 fsub
指令永远不会抛出任何运行时异常。
第 264 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
getfield
操作 获取对象的字段值
格式 getfield
indexbyte1
indexbyte2
结构 getfield = 180(0xb4)
操作数栈 „,objectref →
„,value
当前类(§2.6)的运行时常量池的索引值,构建方式为(indexbyte1 << 8)
| indexbyte2,该索引所指向的运行时常量池项应当是一个字段(§5.1)
的符号引用,它包含了字段的名称和描述符,以及包含该字段的类的符号引用。
这个字段的符号引用是已被解析过的(§5.4.3.2)。指令执行后,被
objectref 所引用的对象中该字段的值将会被取出,并推入到操作数栈顶。
的(§4.6),并且这个字段是当前类的父类成员,并且这个字段没有在同一个
前类或者当前类的子类。
被抛出。
另外,如果已解析的字段是一个静态(static)字段,getfield 指令将会
抛出一个 IncompatibleClassChangeError 异常
第 265 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
NullPointerException.异常。
第 266 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
getstatic
操作 获取对象的静态字段值
格式 getstatic
indexbyte1
indexbyte2
结构 getstatic = 178(0xb2)
操作数栈 „ →
„,value
该索引所指向的运行时常量池项应当是一个字段(§5.1)的符号引用,它包
含了字段的名称和描述符,以及包含该字段的类或接口的符号引用。这个字段
的符号引用是已被解析过的(§5.4.3.2)。
在字段被成功解析之后,如果字段所在的类或者接口没有被初始化过(§5.5),
那指令执行时将会触发其初始化过程。
参数所指定的类或接口的该字段的值将会被取出,并推入到操作数栈顶。
被抛出。
另外,如果已解析的字段是一个非静态(not static)字段,getstatic
指令将会抛出一个 IncompatibleClassChangeError 异常
令就可能抛出在§5.5 中描述到的任何异常。
第 267 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
goto
操作 分支跳转
格式 goto
branchbyte1
branchbyte2
结构 goto = 167(0xa7)
操作数栈 无改变
第 268 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
goto_w
操作 分支跳转(宽范围)
格式 goto_w
branchbyte1
branchbyte2
branchbyte3
branchbyte4
结构 goto_w = 200(0xc8)
操作数栈 无改变
由上述偏移量确定的目标地址上继续执行。这个目标地址必须处于 goto_w 指
令所在的方法之中。
拟机版本中增大。
第 269 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
i2b
格式 i2b
结构 i2b = 145(0x91)
操作数栈 „,value →
„,result
一个 int 的结果压入到操作数栈之中。
原值有不同的正负号。
第 270 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
i2c
格式 i2c
结构 i2c = 146(0x92)
操作数栈 „,value →
„,result
个 int 的结果压入到操作数栈之中。
果永远为正数)与原值有不同的正负号。
第 271 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
i2d
格式 i2d
结构 i2d = 135(0x87)
操作数栈 „,value →
„,result
§5.1.2)
,因为所有 int 类型的数据都可以精确表示为 double 类型的数据,
所以转换是精确的。
第 272 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
i2f
格式 i2f
结构 i2f = 134(0x86)
操作数栈 „,value →
„,result
据,然后压入到操作数栈之中。
效数值位。
第 273 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
i2l
格式 i2l
结构 i2l = 133(0x85)
操作数栈 „,value →
„,result
到操作数栈之中。
所以转换是精确的。
第 274 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
i2s
格式 i2s
结构 i2s = 147(0x93)
操作数栈 „,value →
„,result
一个 int 的结果压入到操作数栈之中。
原值有不同的正负号。
第 275 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iadd
操作 int 类型数据相加
格式 iadd
结构 iadd = 96(0x60)
操作数栈 „,value1,value2 →
„,result
运算的结果使用低位在高地址(Low-Order Bites)的顺序、按照二进制补
果的符号可能与真正数学运算结果的符号相反。
常。
第 276 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iaload
格式 iaload
结构 iaload = 46(0x2e)
操作数栈 „,arrayref,index →
„,value
栈中。
第 277 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iand
操作 对 int 类型数据进行按位与运算
格式 iand
结构 iand = 126(0x7e)
操作数栈 „,value1,value2 →
„,result
第 278 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iastore
格式 iastore
结构 iastore = 79(0x4f)
操作数栈 „,arrayref,index,value →
数组元素中。
异常
第 279 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iconst_<i>
操作 将 int 类型数据压入到操作数栈中
格式 iconst_<i>
结构 iconst_m1 = 2(0x2)
iconst_0 = 3(0x3)
iconst_1 = 4(0x4)
iconst_2 = 5(0x5)
iconst_3 = 6(0x6)
iconst_4 = 7(0x7)
iconst_5 = 8(0x8)
操作数栈 „ →
„,<i>
的作用一致,仅仅除了操作数<i>是隐式包含在指令中这点不同而已。
第 280 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
idiv
操作 int 类型数据除法
格式 idiv
结构 idiv = 108(0x6c)
操作数栈 „,value1,value2 →
„,result
从操作数栈中出栈,并且将这两个数值相除(value1÷value2),结果转换
|d|×|q|≤|n|的前提下取尽可能大的整数值。另外,当|n|≥|d|并且 n 和 d
号为负。
数,除数为-1。那运算时将会发生溢出,运算结果就等于被除数本身。尽管
这里发生了溢出,但是依然不会有异常抛出。
第 281 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
if_acmp<cond>
操作 reference 数据的据条件分支判断
格式 if_acmp<cond>
branchbyte1
branchbyte2
结构 if_acmpeq = 165(0xa5)
if_acmpne = 166(0xa6)
操作数栈 „,value1,value2 →
和 value2 从操作数栈中出栈,然后进行比较运算,比较的规则如下:
| branchbyte2。指令执行后,程序将会转到这个 if_acmp<cond>指令之
后的,由上述偏移量确定的目标地址上继续执行。这个目标地址必须处于
if_acmp<cond>指令所在的方法之中。
另外,如果比较结果为假,那程序将继续执行 if_acmp<cond>指令后面的其
他直接码指令。
第 282 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
if_icmp<cond>
操作 int 数值的条件分支判断
格式 if_icmp<cond>
branchbyte1
branchbyte2
结构 if_icmpeq = 159(0x9f)
if_icmpne = 160(0xa0)
if_icmplt = 161(0xa1)
if_icmpge = 162(0xa2)
if_icmpgt = 163(0xa3)
if_icmple = 164(0xa4)
操作数栈 „,value1,value2 →
从操作数栈中出栈,然后进行比较运算(所有比较都是带符号的),比较的规
则如下:
| branchbyte2。指令执行后,程序将会转到这个 if_icmp<cond>指令之
第 283 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
后的,由上述偏移量确定的目标地址上继续执行。这个目标地址必须处于
if_icmp<cond>指令所在的方法之中。
另外,如果比较结果为假,那程序将继续执行 if_acmp<cond>指令后面的其
他直接码指令。
第 284 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
if<cond>
操作 整数与零比较的条件分支判断
格式 if<cond>
branchbyte1
branchbyte2
结构 ifeq = 153(0x99)
ifne = 154(0x9a)
iflt = 155(0x9b)
ifge = 156(0x9c)
ifgt = 157(0x9d)
ifle = 158(0x9e)
操作数栈 „,value →
与零值进行比较(所有比较都是带符号的),比较的规则如下:
| branchbyte2。指令执行后,程序将会转到这个 if<cond>指令之后的,
由上述偏移量确定的目标地址上继续执行。这个目标地址必须处于 if<cond>
第 285 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
指令所在的方法之中。
另外,如果比较结果为假,那程序将继续执行 if_acmp<cond>指令后面的其
他直接码指令。
第 286 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ifnonnull
操作 引用不为空的条件分支判断
格式 ifnonnull
branchbyte1
branchbyte2
结构 ifnonnull = 199(0xc7)
操作数栈 „,value →
接码指令。
第 287 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ifnull
操作 引用为空的条件分支判断
格式 ifnull
branchbyte1
branchbyte2
结构 fnull = 198(0xc6)
操作数栈 „,value →
指令。
第 288 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
Iinc
操作 局部变量自增
格式 iinc
index
const
结构 iinc = 132(0x84)
操作数栈 无改变
由 index 定位到的局部变量中。
型数值作为索引来访问局部变量表以及令局部变量增加 2 个字节长度的有符号
数值。
第 289 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iload
格式 iload
index
结构 iload = 21(0x15)
操作数栈 „ →
„,value
令执行后,value 将会压入到操作数栈栈顶
byte 型数值作为索引来访问局部变量表。
第 290 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iload_<n>
格式 iload_<n>
结构 iload_0 = 26(0x1a)
iload_1 = 27(0x1b)
iload_2 = 28(0x1c)
iload_3 = 29(0x1d)
操作数栈 „ →
„,value
描述 <n>代表一个当前栈帧(§2.6)中局部变量表的索引值,<n>作为索引定位的
作数栈栈顶
令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 291 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
imul
操作 int 类型数据乘法
格式 imul
结构 imul = 104(0x68)
操作数栈 „,value1,value2 →
„,result
从操作数栈中出栈,接着将这两个数值相乘(value1×value2),结果压入
到操作数栈中。
运算的结果使用低位在高地址(Low-Order Bites)的顺序、按照二进制补
果的符号可能与真正数学运算结果的符号相反。
常。
第 292 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ineg
操作 int 类型数据取负运算
格式 ineg
结构 ineg = 116(0x74)
操作数栈 „,value →
„,result
对这个数进行算术取负运算,运算结果-value 被压入到操作数栈中。
二进制补码来表示整数,而且二进制补码值的范围并不是完全对称的,int 类
型中绝对值最大的负数取反的结果也依然是它本身。尽管指令执行过程中可能
发生上限溢出,但是不会抛出任何异常。
第 293 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
instanceof
操作 判断对象是否指定的类型
格式 instanceof
indexbyte1
indexbyte2
结构 instanceof = 193(0xc1)
操作数栈 „,value →
„,result
当前类(§2.6)的运行时常量池的索引值,构建方式为(indexbyte1 << 8)
| indexbyte2,该索引所指向的运行时常量池项应当是一个类、接口或者数
组类型的符号引用。
到操作数栈栈顶。
另外,如果参数指定的类、接口或者数组类型会被虚拟机解析(§5.4.3.1)。
类、接口或者数组类型,checkcast 指令根据这些规则来判断转换是否成立:
如果 S 是类类型(Class Type),那么:
代表的类型的子类。
第 294 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
如果 S 是接口类型(Interface Type),那么:
如果 T 是接口类型,那么 T 与 S 应当是相同的接口,或者 T 是 S 的
父接口。
件类型为 SC,那么:
那么下面两条规则之一必须成立:
TC 和 SC 是同一个原始类型。
(以此处描述的规则来判断是否互相匹配)。
都可能被抛出。
指令将抛出 ClassCastException 异常
instanceof 是返回一个比较结果)以及指令执行后对操作数栈的影响。
第 295 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
inovkedynamic
操作 调用动态方法
格式 inovkedynamic
indexbyte1
indexbyte2
该索引所指向的运行时常量池项应当是一个调用点限定符(§5.1)的符号引
调用点限定符会被解析(§5.4.3.6)为一个动态调用点,从中可以获取到
java.lang.invoke.MethodHandle 实例的引用、
java.lang.invoke.MethodType 实例的引用和所涉及的静态参数的引用。
接着,作为调用点限定符解析过程的一部分,引导方法将会被执行。如同使用
invokevirtual 指令调用普通方法那样,会包含一个运行时常量池的索引指
向一个带有如下属性的方法(§5.1):
此方法名为 invoke。
此方法描述符中的返回值是 java.lang.invoke.CallSite。
第 296 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
此方法描述符中的参数来源自操作数栈中的元素,包括如下顺序排列的 4
个参数:
java.lang.invoke.MethodHandle
java.lang.invoke.MethodHandles.Lookup
String
java.lang.invoke.MethodType
如果调用点限定符有静态参数,那么这些参数的参数类型应附加在方法描
述符的参数类型中,以便在调用时按顺序入栈至操作数栈。静态参数的参
数类型可以包括:
Class
java.lang.invoke.MethodHandle
java.lang.invoke.MethodType
String
int
long
float
double
在 java.lang.invoke.MethodHandle 之中可以提供关于在哪个类中
能找到方法的符号引用所对应的实际方法的信息。
引导方法执行前,下面各项内容将会按顺序压入到操作数栈中:
用。
用于确定动态调用点发生位置的
java.lang.invoke.MethodHandles.Lookup 对象的引用。
在方法限定符中出现的各种静态参数,包括:类、方法类型、方法句柄、
字符串以及各种数值类型(§2.3.1, §2.3.2)都必须按照他们在方法
限定符中出现的顺序依次入栈(此处基本类型不会发生自动装箱)。
第 297 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
只要引导方法能够被正确调用,它的描述符可以是不精确的。举个例子,引导
方法的第一个参数应该是 java.lang.invoke.MethodHandles.Lookup,
至是全部)上面描述中要压入到操作数栈的参数会被包含在一个数组参数之
中。
引导方法的调用发生在试图解析动态方法调用点的调用点限定符的那条线程
上,如果同时有多条线程进行此操作,那引导方法将会被并发调用。因此,如
果引导方法中有访问公有数据的话,需要注意多线程竞争问题,对公有数据访
问施行适当的保护措施。
择其中一个引导方法的返回值作为调用点对象,并将其发布到所有线程之中。
此动态调用点中其余的引导方法会完成整个执行过程,但是它们的返回结果将
调用点对象拥有一个类型描述符(一个 java.lang.invoke.MethodType
的实例),它必须语义上等同于调用点限定符中方法描述符内所包含的
java.lang.invoke.MethodType 对象。
调用点限定符解析的结果是一个调用点对象,此对象将会与它的动态调用点永
久绑定起来。
绑定于动态调用点的调用点对象所表示的方法句柄将会被调用,这次调用就和
执行 invokevirtual 指令一样,会带有一个指向运行时常量池的索引,它指
向的常量池项是一个方法的符号引用,此方法具备如下属性:
方法名为 invokeExact。
方法描述符为调用点限定符中包含的描述符。
第 298 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
由 java.lang.invoke.MethodHandle 来确定在哪个类中查找方法的
符号引用所对应的方法。
指令执行时,操作数栈中的内容会被虚拟机解释为包含一个调用点对象的引用
定符中的方法描述符保持一致。
另外,在调用点限定符的后续解析过程中,如果引导方法执行过程因异常 E 而
BootstrapMethodError 异常。
(这可能是由于引导方式有错误的参数长度、参数类型或者返回值而导致
java.lang.invoke.MethodHandle.invoke 方法抛出了
java.lang.invoke.WrongMethodTypeException 异常。
)
另外,在调用点限定符的后续解析过程中,如果引导方法的返回值不是一个
出 BootstrapMethodError 异常。
另外,在调用点限定符的后续解析过程中,如果调用点对象的目标的类型描述
抛出 BootstrapMethodError 异常。
运行时异常 如果动态调用点的调用点限定符解析过程成功完成,那就意味着将有一个非空
的 java.lang.invoke.CallSite 的实例绑定到该动态调用点之上。因此,
操作数栈中表示调用点目标的对象不会为空,这也意味着,调用点限定符中的
的类型描述符语义上是一致的。
java.lang.invoke.WrongMethodTypeException 异常。
第 299 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
invokeinterface
操作 调用接口方法
格式 invokeinterface
indexbyte1
indexbyte2
count
结构 invokeinterface = 185(0xb9)
该索引所指向的运行时常量池项应当是一个接口方法(§5.1)的符号引用,
它包含了方法的名称和描述符,以及包含该方法的接口的符号引用。这个方法
的符号引用是已被解析过的(§5.4.3.2),而且这个方法不能是实例初始化
方法(§2.9)和类或接口的初始化方法(§2.9)。
连续 n 个参数值,这些参数的数值、数据类型和顺序都必须遵循接口方法的描
的 0。
假设 C 是 objectref 所对应的类,虚拟机将按下面规则查找实际执行的方法:
如果 C 中包含了名称和描述符都和要调用的接口方法一致的方法,那这个
方法就会被调用,查找过程终止。
第 300 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
类中能搜索到名称和描述符都和要调用的接口方法一致的方法,那这个方
法就会被调用。
那将占用局部变量 1 和 2 两个位置)
,依此类推。参数中的浮点类型数据在存
入局部变量之前会先进行数值集合转换(§2.8.3)。新栈帧创建后就成为当
从这里开始继续执行。
如果要调用的是本地方法,要是这些平台相关的代码尚未绑定(§5.6)到虚
将从操作数栈中出栈并作为参数传递给实现此方法的代码。参数中的浮点类型
数据在传递给调用方法之前会先进行数值集合转换(§2.8.3)。参数传递和
代码执行都会以具体虚拟机实现相关的方式进行。当这些平台相关的代码返回
时:
如果这个本地方法有返回值,那平台相关的代码返回的数据必须通过某种
中。
都可能被抛出。
第 301 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
NullPointerException 异常。
另外,如果没有找到任何名称和描述符都与要调用的接口方法一致的方法,那
抛出 IllegalAccessError 异常。
抛出 AbstractMethodError 异常。
其实这些信息完全可以从方法的描述符中获取到,有这个参数完全是历史原
因。
额外操作数而预留的空间,invokeinterface 指令会在运行时被替换为特殊
的其他指令,这必须要保持向后兼容性。
此在参数传递的时候,可能需要比参数个数更多的局部变量。
第 302 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
invokespecial
操作 调用实例方法;专门用来处理调用超类方法、使用方法和实例初始化方法方法。
格式 invokespecial
indexbyte1
indexbyte2
结构 invokespecial = 183(0xb7)
该索引所指向的运行时常量池项应当是一个方法(§5.1)的符号引用,它包
含了方法的名称和描述符,以及包含该方法的接口的符号引用。最后,如果调
且这个方法没有在同一个运行时包(§5.3)中定义过,那 objectref 所指
向的对象的类型必须为当前类或者当前类的子类。
只有下面所有的条件都成立的前提下,才会进行调用方法的搜索:
调用方法所在的类似当前类的超类。
调用方法不是实例初始化方法(§2.9)。
如果以上条件都成立,虚拟机将按下面规则查找实际执行的方法,假设 C 是当
前类的直接父类:
如果 C 中包含了名称和描述符都和要调用的实例方法一致的方法,那这个
方法就会被调用,查找过程终止。
类中能搜索到名称和描述符都和要调用的实例方法一致的方法,那这个方
第 303 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
法就会被调用。
之后还跟随着连续 n 个参数值,这些参数的数值、数据类型和顺序都必须遵循
实例方法的描述符中的描述。
那将占用局部变量 1 和 2 两个位置)
,依此类推。参数中的浮点类型数据在存
入局部变量之前会先进行数值集合转换(§2.8.3)。新栈帧创建后就成为当
从这里开始继续执行。
如果要调用的是本地方法,要是这些平台相关的代码尚未绑定(§5.6)到虚
将从操作数栈中出栈并作为参数传递给实现此方法的代码。参数中的浮点类型
数据在传递给调用方法之前会先进行数值集合转换(§2.8.3)。参数传递和
代码执行都会以具体虚拟机实现相关的方式进行。当这些平台相关的代码返回
时:
如果这个本地方法有返回值,那平台相关的代码返回的数据必须通过某种
中。
都可能被抛出。
第 304 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
另外,如果调用方法是实例初始化方法,但是定义这个方法的类与指令参数中
NoSuchMethodError 异常。
NullPointerException 异常。
另外,如果没有找到任何名称和描述符都与要调用的接口方法一致的方法,那
常。
例初始化方法(§2.9)、private 方法和当前类的超类中的方法。
invokenonvirtual。
此在参数传递的时候,可能需要比参数个数更多的局部变量。
第 305 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
invokestatic
操作 调用静态方法
格式 invokestatic
indexbyte1
indexbyte2
结构 invokestatic = 184(0xb8)
该索引所指向的运行时常量池项应当是一个方法(§5.1)的符号引用,它包
含了方法的名称和描述符(§4.3.3),以及包含该方法的接口的符号引用。
此方法应是已被解析过(§5.4.3.3)的,而且不能是实例的初始化方法和类
或接口的初始化方法。这个方法必须被声明为 static,因此它也不能是
abstract 方法。
在方法被成功解析之后,如果方法所在的类没有被初始化过(§5.5),那指令
执行时将会触发其初始化过程。
在操作数栈中必须包含连续 n 个参数值,这些参数的数值、数据类型和顺序都
必须遵循实例方法的描述符中的描述。
第 306 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
点类型数据在存入局部变量之前会先进行数值集合转换(§2.8.3)。新栈帧
条指令,程序就从这里开始继续执行。
如果要调用的是本地方法,要是这些平台相关的代码尚未绑定(§5.6)到虚
中出栈并作为参数传递给实现此方法的代码。参数中的浮点类型数据在传递给
调用方法之前会先进行数值集合转换(§2.8.3)。参数传递和代码执行都会
以具体虚拟机实现相关的方式进行。当这些平台相关的代码返回时:
状态将会被更新,也可能退出了,就如当前线程中同执行了 monitorexit
指令一般。
如果这个本地方法有返回值,那平台相关的代码返回的数据必须通过某种
中。
都可能被抛出。
IncompatibleClassChangeError 异常。
指令有可能所有在§5.5 中描述过的异常。
注意 方法调用使用到的 n 个参数的总个数并非与局部变量表的个数是一一对应的,
因此在参数传递的时候,可能需要比参数个数更多的局部变量。
第 307 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
invokevirtual
操作 调用实例方法,依据实例的类型进行分派
格式 invokevirtual
indexbyte1
indexbyte2
结构 invokevirtual = 182(0xb6)
该索引所指向的运行时常量池项应当是一个方法(§5.1)的符号引用,它包
含了方法的名称和描述符,以及包含该方法的接口的符号引用。这个方法的符
号引用是已被解析过的(§5.4.3.3),而且这个方法不能是实例初始化方法
(§2.9)和类或接口的初始化方法(§2.9)。最后,如果调用的方法是
protected 的(§4.6)
,并且这个方法是当前类的父类成员,并且这个方法
类型必须为当前类或者当前类的子类。
假设 C 是 objectref 所对应的类,虚拟机将按下面规则查找实际执行的方法:
如果 C 中定义了一个实例方法 M,它了重写(override,§5.4.3.5)
了符号引用中表示的方法,那方法 M 就会被调用,查找过程终止。
父类,如果超类中能搜索到符合的方法,那这个方法就会被调用。
序都必须与方法描述符所描述的保持一致。
第 308 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
那将占用局部变量 1 和 2 两个位置)
,依此类推。参数中的浮点类型数据在存
入局部变量之前会先进行数值集合转换(§2.8.3)。新栈帧创建后就成为当
从这里开始继续执行。
如果要调用的是本地方法,要是这些平台相关的代码尚未绑定(§5.6)到虚
将从操作数栈中出栈并作为参数传递给实现此方法的代码。参数中的浮点类型
数据在传递给调用方法之前会先进行数值集合转换(§2.8.3)。参数传递和
代码执行都会以具体虚拟机实现相关的方式进行。当这些平台相关的代码返回
时:
如果这个本地方法有返回值,那平台相关的代码返回的数据必须通过某种
中。
都可能被抛出。
NullPointerException 异常。
第 309 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
另外,如果没有找到任何名称和描述符都与要调用的接口方法一致的方法,那
AbstractMethodError 异常。
出 AbstractMethodError 异常。
此在参数传递的时候,可能需要比参数个数更多的局部变量。
第 310 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ior
操作 int 类型数值的布尔或运算
格式 ior
结构 ior = 128(0x80)
操作数栈 „,value1,value2 →
„,result
果 result 被压入到操作数栈中。
第 311 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
irem
操作 int 类型数据求余
格式 irem
结构 irem = 112(0x70)
操作数栈 „,value1,value2 →
„,result
结果,然后把运算结果入栈回操作数栈中。
负数,当被除数为正数时余数才能是正数的规则。另外,irem 运算结果的绝
对值永远小于除数的绝对值。
第 312 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ireturn
格式 ireturn
结构 ireturn = 172(0xac)
操作数栈 „,value →
[empty]
synchronized)方法,那在方法调用时进入或者重入的管程应当被正确更新
到调用者栈帧的操作数栈中,在当前栈帧操作数栈中所有其他的值都将会被丢
弃掉。
指令执行后,解释器会恢复调用者的栈帧,并且把程序控制权交回到调用者。
当前方法是一个同步方法,但当前线程在调用方法时没有成功持有(Enter)
IllegalMonitorStateException 异常。这是可能出现的,譬如一个同步
monitorenter 指令。
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,ireturn 指令也会抛出
IllegalMonitorStateException 异常。
第 313 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ishl
操作 int 数值左移运算
格式 ishl
结构 ishl = 120(0x78)
操作数栈 „,value1,value2 →
„,result
值,计算后把运算结果入栈回操作数栈中。
0x1f 做一遍算术与操作。
第 314 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ishr
操作 int 数值右移运算
格式 ishr
结构 ishr = 122(0x7a)
操作数栈 „,value1,value2 →
„,result
值,计算后把运算结果入栈回操作数栈中。
2 的 s 次方。位移的距离实际上被限制在 0 到 31 之间,相当于指令执行时会
第 315 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
istore
格式 istore
index
结构 istore = 54(0x36)
操作数栈 „,value →
数作为索引来访问局部变量表。
第 316 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
istore_<n>
格式 istore_<n>
结构 istore_0 = 59(0x3b)
istore_1 = 60(0x3c)
istore_2 = 61(0x3d)
istore_3 = 62(0x3e)
操作数栈 „,value →
描述 <n>必须是一个指向当前栈帧(§2.6)局部变量表的索引值,而在操作数栈
存到<n>所指向的局部变量表位置中。
指令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 317 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
isub
操作 int 类型数据相减
格式 isub
结构 isub = 100(0x64)
操作数栈 „,value1,value2 →
„,result
运算的结果使用低位在高地址(Low-Order Bites)的顺序、按照二进制补
果的符号可能与真正数学运算结果的符号相反。
常。
第 318 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
iushr
操作 int 数值逻辑右移运算
格式 iushr
结构 iushr = 124(0x7c)
操作数栈 „,value1,value2 →
„,result
值,计算后把运算结果入栈回操作数栈中。
到 31 之间,
第 319 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ixor
操作 int 数值异或运算
格式 ixor
结构 ixor = 130(0x82)
操作数栈 „,value1,value2 →
„,result
结果入栈回操作数栈中。
第 320 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
jsr
操作 程序段落跳转
格式 jsr
branchbyte1
branchbyte2
结构 jsr = 168(0xa8)
操作数栈 „,→
„,address
指令执行时,将产生一个当前位置的偏移坐标,并压入到操作数栈中。跳转目
它取出,这种不对称的操作是故意设计的。
第 321 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
jsr_w
操作 程序段落跳转
格式 jsr_w
branchbyte1
branchbyte2
branchbyte3
branchbyte4
结构 jsr_w = 201(0xc9)
操作数栈 „,→
„,address
8)|branchbyte2。指令执行时,将产生一个当前位置的偏移坐标,并压入
操作数栈,ret 指令从局部变量表中把它取出,这种不对称的操作是故意设计
的。
的 Java 虚拟机中被提升。
第 322 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
l2d
格式 l2d
结构 l2d = 138(0x8a)
操作数栈 „,value →
„,result
据,然后压入到操作数栈之中。
§5.1.2)
,由于 double 类型只有 53 位有效位数,所以转换可能会产生精度
丢失。
第 323 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
l2f
格式 l2f
结构 l2f = 137(0x89)
操作数栈 „,value →
„,result
据,然后压入到操作数栈之中。
§5.1.2)
,由于 double 类型只有 24 位有效位数,所以转换可能会产生精度
丢失。
第 324 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
l2i
格式 l2i
结构 l2i = 136(0x88)
操作数栈 „,value →
„,result
后压入到操作数栈之中。
原值有不同的正负号。
第 325 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ladd
操作 long 类型数据相加
格式 ladd
结构 ladd = 97(0x61)
操作数栈 „,value1,value2 →
„,result
运算的结果使用低位在高地址(Low-Order Bites)的顺序、按照二进制补
果的符号可能与真正数学运算结果的符号相反。
常。
第 326 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
laload
格式 laload
结构 laload = 47(0x2f)
操作数栈 „,arrayref,index →
„,value
栈中。
第 327 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
land
操作 对 long 类型数据进行按位与运算
格式 land
结构 land = 127(0x7f)
操作数栈 „,value1,value2 →
„,result
第 328 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lastore
格式 lastore
结构 lastore = 80(0x50)
操作数栈 „,arrayref,index,value →
作为索引定位到数组元素中。
异常
第 329 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lcmp
操作 比较 2 个 long 类型数据的大小
格式 lcmp
结构 lcmp = 148(0x94)
操作数栈 „,value1,value2 →
„,result
小于 value2,结果为-1,最后比较结果被压入到操作数栈中。
第 330 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lconst_<l>
操作 将 long 类型数据压入到操作数栈中
格式 lconst_<l>
结构 lconst_0 = 9(0x9)
lconst_1 = 10(0xa)
操作数栈 „ →
„,<l>
第 331 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ldc
操作 从运行时常量池中提取数据推入操作数栈
格式 ldc
index
结构 ldc = 18(0x12)
操作数栈 „ →
„,value
的运行时常量,或者是一个类的符号引用(§5.4.3.1)或者字符串字面量
(§5.1)。
另外,如果运行时常量池项必须是一个代表字符串字面量(§5.1)的 String
到操作数栈之中。
另外,如果运行时常量池项必须是一个类的符号引用(§4.4.1),这个符号
第 332 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ldc_w
操作 从运行时常量池中提取数据推入操作数栈(宽索引)
格式 ldc_w
indexbyte1
indexbyte2
结构 ldc_w = 19(0x13)
操作数栈 „ →
„,value
量,或者是一个类的符号引用(§5.4.3.1)或者字符串字面量(§5.1)。
另外,如果运行时常量池项必须是一个代表字符串字面量(§5.1)的 String
到操作数栈之中。
另外,如果运行时常量池项必须是一个类的符号引用(§4.4.1),这个符号
第 333 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ldc2_w
格式 ldc2_w
indexbyte1
indexbyte2
结构 ldc2_w = 20(0x14)
操作数栈 „ →
„,value
常量(§5.1)。数值这个常量所对应的数值将被压入到操作数栈之中。
选取。
第 334 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ldiv
操作 long 类型数据除法
格式 ldiv
结构 ldiv = 109(0x6d)
操作数栈 „,value1,value2 →
„,result
从操作数栈中出栈,并且将这两个数值相除(value1÷value2),结果转换
|d|×|q|≤|n|的前提下取尽可能大的整数值。另外,当|n|≥|d|并且 n 和 d
号为负。
负数,除数为-1。那运算时将会发生溢出,运算结果就等于被除数本身。尽
管这里发生了溢出,但是依然不会有异常抛出。
第 335 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lload
格式 lload
index
结构 iload = 22(0x16)
操作数栈 „ →
„,value
(§2.6)中局部变量表的索引的,index 作为索引定位的局部变量必须为
byte 型数值作为索引来访问局部变量表。
第 336 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lload_<n>
格式 lload_<n>
结构 lload_0 = 30(0x1e)
lload_1 = 31(0x1f)
lload_2 = 32(0x20)
lload_3 = 33(0x21)
操作数栈 „ →
„,value
描述 <n>与<n>+1 共同构成一个当前栈帧(§2.6)中局部变量表的索引的,<n>
将会压入到操作数栈栈顶
令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 337 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lmul
操作 long 类型数据乘法
格式 lmul
结构 lmul = 105(0x69)
操作数栈 „,value1,value2 →
„,result
从操作数栈中出栈,接着将这两个数值相乘(value1×value2),结果压入
到操作数栈中。
运算的结果使用低位在高地址(Low-Order Bites)的顺序、按照二进制补
果的符号可能与真正数学运算结果的符号相反。
常。
第 338 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lneg
操作 long 类型数据取负运算
格式 lneg
结构 lneg = 117(0x75)
操作数栈 „,value →
„,result
对这个数进行算术取负运算,运算结果-value 被压入到操作数栈中。
用二进制补码来表示整数,而且二进制补码值的范围并不是完全对称的,long
类型中绝对值最大的负数取反的结果也依然是它本身。尽管指令执行过程中可
能发生上限溢出,但是不会抛出任何异常。
第 339 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lookupswitch
操作 根据键值在跳转表中寻找配对的分支并进行跳转
格式 lookupswitch
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
npairs1
npairs2
npairs3
npairs4
match-offset pairs…
结构 lookupswitch = 171(0xab)
操作数栈 „,key →
4 个字节组成的、从当前方法开始(第一条操作码指令)计算的地址,即紧跟
第 340 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
升序储存。
用 default 作为目标地址进行跳转。程序从目标地址开始继续执行。
内。
所有的匹配坐标以有序方式存储是为了查找时的效率考虑。
第 341 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lor
操作 long 类型数值的布尔或运算
格式 lor
结构 lor = 129(0x81)
操作数栈 „,value1,value2 →
„,result
第 342 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lrem
操作 long 类型数据求余
格式 lrem
结构 lrem = 113(0x71)
操作数栈 „,value1,value2 →
„,result
结果,然后把运算结果入栈回操作数栈中。
能是负数,当被除数为正数时余数才能是正数的规则。另外,lrem 运算结果
的绝对值永远小于除数的绝对值。
第 343 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lreturn
格式 lreturn
结构 lreturn = 173(0xad)
操作数栈 „,value →
[empty]
如果当前方法是一个同步(声明为 synchronized)方法,那在方法调用时
进入或者重入的管程应当被正确更新状态或退出,就像当前线程执行了
将从当前栈帧(§2.6)中出栈,然后压入到调用者栈帧的操作数栈中,在当
前栈帧操作数栈中所有其他的值都将会被丢弃掉。
指令执行后,解释器会恢复调用者的栈帧,并且把程序控制权交回到调用者。
当前方法是一个同步方法,但当前线程在调用方法时没有成功持有(Enter)
IllegalMonitorStateException 异常。这是可能出现的,譬如一个同步
monitorenter 指令。
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,lreturn 指令也会抛出
IllegalMonitorStateException 异常。
第 344 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lshl
操作 long 数值左移运算
格式 lshl
结构 lshl = 121(0x79)
操作数栈 „,value1,value2 →
„,result
值,计算后把运算结果入栈回操作数栈中。
0x3f 做一遍算术与操作。
第 345 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lshr
操作 long 数值右移运算
格式 lshr
结构 lshr = 123(0x7b)
操作数栈 „,value1,value2 →
„,result
值,计算后把运算结果入栈回操作数栈中。
2 的 s 次方。位移的距离实际上被限制在 0 到 63 之间,相当于指令执行时会
第 346 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lstore
格式 lstore
index
结构 lstore = 55(0x37)
操作数栈 „,value →
的局部变量表位置中。
数作为索引来访问局部变量表。
第 347 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lstore_<n>
格式 lstore_<n>
结构 lstore_0 = 63(0x3f)
lstore_1 = 64(0x40)
lstore_2 = 65(0x41)
lstore_3 = 66(0x42)
操作数栈 „,value →
描述 <n>与<n>+1 共同表示一个当前栈帧(§2.6)局部变量表的索引值,而在操
然后保存到<n>及<n>+1 所指向的局部变量表位置中。
指令作的作用一致,仅仅除了操作数<n>是隐式包含在指令中这点不同而已。
第 348 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lsub
操作 long 类型数据相减
格式 lsub
结构 lsub = 101(0x65)
操作数栈 „,value1,value2 →
„,result
运算的结果使用低位在高地址(Low-Order Bites)的顺序、按照二进制补
果的符号可能与真正数学运算结果的符号相反。
常。
第 349 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lushr
操作 long 数值逻辑右移运算
格式 lushr
结构 lushr = 125(0x7d)
操作数栈 „,value1,value2 →
„,result
值,计算后把运算结果入栈回操作数栈中。
在 0 到 63 之间,
第 350 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
lxor
操作 long 数值异或运算
格式 lxor
结构 lxor = 131(0x83)
操作数栈 „,value1,value2 →
„,result
结果入栈回操作数栈中。
第 351 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
monitorenter
操作 进入一个对象的 monitor
格式 monitorenter
结构 monitorenter = 194(0xc2)
操作数栈 „,objectref →
个 monitor,重入时需将进入计数器的值加 1。
所有权。
NullPointerException 异常。
第 352 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
规范的范围之外,但可以稍作介绍。monitor 即可以实现为与对象一同分配
和销毁,也可以在某条线程尝试获取对象所有权时动态生成,在没有任何线程
持有对象所有权时自动释放。
还包括有等待(Object.wait)和唤醒(Object.notifyAll 和
第 353 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
monitorexit
操作 退出一个对象的 monitor
格式 monitorexit
结构 monitorexit = 195(0xc3)
操作数栈 „,objectref →
者。
指令执行时,
线程把 monitor 的进入计数器值减 1,如果减 1 后计数器值为 0,
NullPointerException 异常。
IllegalMonitorStateException.异常。
语义,尽管它们确实可以实现类似的语义。
第 354 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
的异常有不同的处理方式:
athrow 指令退出。
monitor。
第 355 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
multianewarray
操作 创建一个新的多维数组
格式 multianewarray
indexbyte1
indexbyte2
dimensions
结构 multianewarray = 197(0xc5)
操作数栈 „,count1,[count2,…] →
„,arrayref
数组每一个值代表每个维度中需要创建的元素数量。这些值必须为非负数 int
依此类推。
indexbyte2 用于构建一个当前类(§2.6)的运行时常量池的索引值,构建
池项应当是一个类、接口或者数组类型的符号引用,这个类、接口或者数组类
型应当是已被解析(§5.4.3.1)的。指令执行产生的结果将会是一个维度不
小于 dimensions 的数组。
不会分配维度。数组第一维的元素被初始化为第二维的子数组,后面每一维都
依此类推。数组的最后一个维度的元素将会被分配为数组元素类型的初始值
压入到操作数栈中。
第 356 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
都可能被抛出。
另外,如果当前类没有权限访问数组的元素类型,multianewarray 指令将
个 NegativeArraySizeException 异常。
在运行时常量池中确定的数组类型维度可能比操作数栈中 dimensions 所代
表的维度更高,在这种情况下,multianewarray 指令只会创建数组的第一
个维度。
第 357 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
new
操作 创建一个对象
格式 new
indexbyte1
indexbyte2
结构 new = 187(0xbb)
操作数栈 „ →
„,objectref
该索引所指向的运行时常量池项应当是一个类或接口的符号引用,这个类或接
口类型应当是已被解析(§5.4.3.1)并且最终解析结果为某个具体的类型。
一个以此为类型为对象将会被分配在 GC 堆中,并且它所有的实例变量都会进
行初始化为相应类型的初始值(§2.3,§2.4)
。一个代表该对象实例的
对于一个已成功解析但是未初始化(§5.5)的类型,在这时将会进行初始化。
都可能被抛出。
另外,如果在类、接口或者数组的符号引用最终被解析为一个接口或抽象类,
言规范》§15.9.4 中所描述的异常。
注意 new 指令执行后并没有完成一个对象实例创建的全部过程,只有实例初始化方
第 358 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
法被执行并完成后,实例才算完全创建。
第 359 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
newarray
操作 创建一个数组
格式 newarray
atype
结构 newarray = 188(0xbc)
操作数栈 „,count →
„,arrayref
创建多大的数组。
atype 为要创建数组的元素类型,它将为以下值之一:
数组类型 atype
T_BOOLEAN 4
T_CHAR 5
T_FLOAT 6
T_DOUBLE 7
T_BYTE 8
T_SHORT 9
T_INT 10
T_LONG 11
这个新数组的所有元素将会被分配为相应类型的初始值(§2.3,§2.4)。
NegativeArraySizeException 异常。
第 360 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
以 8 位储存,
并使用 baload 和 bastore 指令操作,这些指令也可以操作 byte
第 361 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
nop
操作 什么事情都不做
格式 nop
结构 nop = 0(0x0)
操作数栈 无变化
描述 什么事情都不做
第 362 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
pop
操作 将操作数栈的栈顶元素出栈
格式 pop
结构 pop = 87(0x57)
操作数栈 „,value →
描述 将操作数栈的栈顶元素出栈。
pop 指令只能用来操作(§2.11.1)中定义的分类一的运算类型。
第 363 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
pop2
操作 将操作数栈的栈顶一个或两个元素出栈
格式 pop2
结构 pop2 = 88(0x58)
操作数栈 结构 1:
„,value2,value1 →
型。
结构 2:
„,value →
描述 将操作数栈的栈顶一个或两个元素出栈。
第 364 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
putfield
操作 设置对象字段
格式 putfield
indexbyte1
indexbyte2
结构 putfield = 181(0xb5)
操作数栈 „,objectref,value →
该索引所指向的运行时常量池项应当是一个字段(§5.1)的符号引用,它包
含了字段的名称和描述符,以及包含该字段的类的符号引用。objectref 所
且这个字段是当前类的父类成员,并且这个字段没有在同一个运行时包
当前类的子类。
第 365 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
被抛出。
另外,如果已解析的字段是一个静态(static)字段,getfield 指令将会
另外,如果字段声明为 final,那就只有在当前类的实例初始化方法(<init>)
IllegalAccessError 异常。
NullPointerException.异常。
第 366 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
putstatic
操作 设置对象的静态字段值
格式 putstatic
indexbyte1
indexbyte2
结构 putstatic = 179(0xb3)
操作数栈 „,value →
该索引所指向的运行时常量池项应当是一个字段(§5.1)的符号引用,它包
含了字段的名称和描述符,以及包含该字段的类或接口的符号引用。这个字段
的符号引用是已被解析过的(§5.4.3.2)。
在字段被成功解析之后,如果字段所在的类或者接口没有被初始化过(§5.5),
那指令执行时将会触发其初始化过程。
配(§4.3.2)。如果字段描述符的类型是 boolean、byte、char、short
指令执行时,value 从操作数栈中出栈,根据(§2.8.3)中定义的转换规则
第 367 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
被抛出。
另外,如果已解析的字段是一个非静态(not static)字段,putstatic
另外,如果字段声明为 final,那就只有在当前类的实例初始化方法
IllegalAccessError 异常。
注意 putstatic 指令只有在接口初始化时才能用来设置接口字段的值,接口字段
只会在接口初始化的时候初始化赋值一次(§5.5,JLS §9.3.1)。
第 368 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
ret
操作 代码片段中返回
格式 ret
index
结构 ret = 169(0xa9)
操作数栈 无变化
程序从修改后的位置继续执行。
到操作数栈,ret 指令从局部变量表中把它取出,这种不对称的操作是故意设
计的。
时使用。
为索引来访问局部变量表。
第 369 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
Ireturn
操作 无返回值的方法返回
格式 Return
结构 return = 177(0xb1)
操作数栈 „ →
[empty]
描述 当前方法的返回值必须被声明为 void。如果当前方法是一个同步(声明为
synchronized)方法,那在方法调用时进入或者重入的管程应当被正确更新
中没有异常被抛出的话,在当前栈帧操作数栈中所有其他的值都将会被丢弃
掉。
指令执行后,解释器会恢复调用者的栈帧,并且把程序控制权交回到调用者。
当前方法是一个同步方法,但当前线程在调用方法时没有成功持有(Enter)
IllegalMonitorStateException 异常。这是可能出现的,譬如一个同步
monitorenter 指令。
另外,如果虚拟机实现严格执行了§2.11.10 中规定的结构化锁定规则,但
当前方法调用时,其中的第一条规则被违反的话,return 指令也会抛出
IllegalMonitorStateException 异常。
第 370 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
saload
格式 saload
结构 saload = 53(0x35)
操作数栈 „,arrayref,index →
„,value
操作数栈中。
第 371 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
sastore
格式 sastore
结构 sastore = 86(0x56)
操作数栈 „,arrayref,index,value →
异常
第 372 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
sipush
格式 sipush
byte1
byte2
结构 sipush = 17(0x11)
操作数栈 „ →
„,value
将 value 压入到操作数栈中。
第 373 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
swap
操作 交换操作数栈顶的两个值
格式 swap
结构 swap = 95(0x5f)
操作数栈 „,value2,value1 →
„,value1,value2
描述 交换操作数栈顶的两个值。
运算类型才能使用。
Java 虚拟机为提供交换操作数栈中两个分类二数值的指令。
第 374 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
tableswitch
操作 根据索引值在跳转表中寻找配对的分支并进行跳转
格式 tableswitch
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
lowbyte1
lowbyte2
lowbyte3
lowbyte4
highbyte1
highbyte2
highbyte3
highbyte4
jump offsets…
结构 tableswitch = 170(0xaa)
操作数栈 „,index →
个字节组成的、从当前方法开始(第一条操作码指令)计算的地址,即紧跟随
第 375 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
byte4 方式构成。
继续执行。
第 376 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
wide
操作 扩展局部变量表索引
格式 1 wide
<opcode>
indexbyte1
indexbyte2
当<opcode>为 iload,fload,aload,lload,dload,istore,fstore,
格式 2 wide
iinc
indexbyte1
indexbyte2
constbyte1
constbyte2
结构 wide = 196(0xc4)
操作数栈 与被扩展的指令一致
描述 wide 指令用于扩展其他指令的行为,取决于被不同扩展的指令,它可以有两
种形式。第一种形式是当被扩展指令为 iload,fload,aload,lload,
无论哪种形式,wide 指令后面都跟随者被扩展指令的操作码,之后是两个无
16 位无符号索引。随后,wide 使用这个被新计算出来的索引值替换掉被扩展
第 377 页 / 共 387 页
Java 虚拟机规范 — 第 6 章 Java 虚拟机指令集
被 wide 指令扩展的那些指令,行为上与原有指令的语义没有任何区别,仅仅
加范围。
节码指令。
第 378 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
第 7 章 操作码助记符
留操作码(§6.2)到指令助记符的对应。
索引)。
①
译者注:“指令含义”一列在规范原文中并不存在,是为了方便读者使用,译者自己加入的。
第 379 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
第 380 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
第 381 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
它)。
将复制值压入栈顶。
的)。
第 382 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
第 383 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
顶。
顶。
顶。
顶。
顶。
顶。
顶。
顶。
第 384 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
顶;当其中一个数值为“NaN”时,将-1 压入栈顶。
顶;当其中一个数值为“NaN”时,将 1 压入栈顶。
顶;当其中一个数值为“NaN”时,将-1 压入栈顶。
顶;当其中一个数值为“NaN”时,将 1 压入栈顶。
栈顶。
联合使用)。
第 385 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
并将其引用值压入栈顶。
入栈顶。
0 压入栈顶。
①
操作码为 186(0xba)的 invokedynamic 指令是 Java SE 7 中新加入的。
第 386 页 / 共 387 页
Java 虚拟机规范 — 第 7 章 操作码助记符
必须包含各维度的长度值),并将其引用值压入栈顶。
压入栈顶。
保留指令
第 387 页 / 共 387 页