本章主要翻译自《The Java Virtual Machine Specification》(下面简称“《JVMs》”)的第6.4章节,再结合上自己的理解来捋清楚如何看懂指令手册。


JVM 里提供了许多的指令,但是初学者很难找到并看懂各个指令的含义,所以这里讲讲如何看懂JVM指令手册
我们平常说的 aload_1astore 这些指令其实都称为助记符(mnemonic)。因为它仅仅是为了人类可读而存在的 JVM 实际执行时都是使用得 opcode 。就像汇编语言,我们写的时候都是使用的指令,实际编译后都是二进制~

接下来看看在《JVMs》里,如何看懂一个指令的描述
image.png
上面这幅图是介绍一个指令的常规布局,我将这部分分析划分为两个部分,分别为主要的描述,还有不太重要的附加描述。

主要部分

image.png

Each cell in the instruction format diagram represents a single 8-bit byte. The instruction’s mnemonic is its name.

该句表示 Format 图示里面的每一个单元(就是每一个长方形小框框)都代表一个字节(8位)。指令的助记符就是指令的名字

Its opcode is its numeric representation and is given in both decimal and hexadecimal forms. Only the numeric representation is actually present in the Java Virtual Machine code in a class file.

“opcode”使用数字描述,后面的指令集里面给出了十进制和十六进制的表示形式。在JVM真实的字节码文件中,就是用opcode来表示一个指令的(毕竟撇开那些字节码查看工具,直接看二进制文件,它实际上是下图所示的这样子)。
image.png

Keep in mind that there are “operands” generated at compile time and embedded within Java Virtual Machine instructions, as well as “operands” calculated at run time and supplied on the operand stack.

要记住,这里说的操作数,有一些是在编译期间就插入到指令集中的;也有一些是在运行时计算,然后把结果放到操作数栈上的。
这句话光看英文有点绕,我结合了一下上下文举两个例子,比如有一个 bipush 指令,它是在编译器就把操作数插入到指令里去了;又比如 iadd 指令,它在运行时会将栈顶的两个元素弹出,运算后把结果放入操作数栈顶。

Although they are supplied from several different areas, all these operands represent the same thing: values to be operated upon by the Java Virtual Machine instruction being executed.

虽然这些操作数来自于不同的区域,但是所有的这些都只为了传达一个信息:只有在指令被执行时才会操作这些数值。

By implicitly taking many of its operands from its operand stack, rather than representing them explicitly in its compiled code as additional operand bytes, register numbers, etc.,the Java Virtual Machine’s code stays compact.

通过操作数栈隐式获取操作数,而不是用额外的字节来描述这些操作数、寄存器编号等,这种方案使得字节码文件变得很紧凑。
怎么理解呢?就是说现在有两种方案,第一种比第二种更好一些:

  1. 将操作数压入栈/弹出栈,来获取/计算操作数。虽然优点麻烦,但是让字节码很紧凑
  2. 用额外的字节码、寄存器编号等等手段给这些操作数“打标记”。虽然使用起来很直接,但字节码文件肯定会庞大很多(因为需要保存额外的信息)

Some instructions are presented as members of a family of related instructions sharing a single description, format, and operand stack diagram.

有一些指令是以成员的形式存在于 相似指令的一个族群内,该族群指令会分享一个描述(description)、格式(format)、操作数栈图(operan stack diagram)。简单来说就是有部分指令本质上是一个指令,比如 aload_<n> 指令,该指令里的 <n> 可以任意变化,比如 aload_1aload_2aload_9 等等,但这些都属于 aload_ 指令集。然后 aload_1aload_2aload_3 这些指令的描述都和 aload_<n> 一样共享相同的资料。

As such, a family of instructions includes several opcodes and opcode mnemonics; only the family mnemonic appears in the instruction format diagram, and a separate forms line lists all member mnemonics and opcodes.

因此,一个族群的指令会包含多种opcode和opcode助记符。format 里会出现在指令的助记符,然后 forms 里面会列出其他成员助记符和opcode。后面看例子就懂了

For example, the Forms line for the lconst_ family of instructions, giving mnemonic and opcode information for the two instructions in that family (lconst_0 and lconst_1), is lconst_0 = 9 (0x9) lconst_1 = 10 (0xa)

举个例子, lconst_<i> 指令的 forms 里面给出了两种同族群的指令:
image.png
然后 format 里面列出了 lconst 的助记符

In the description of the Java Virtual Machine instructions, the effect of an instruction’s execution on the operand stack (§2.6.2) of the current frame (§2.6) is represented textually, with the stack growing from left to right and each value represented separately.

在指令介绍中,以文本形式表示指令执行对当前栈帧的操作数堆栈的影响,堆栈从左到右递增,每一个值分开表示:

…, value1, value2 →

上面这个就表示操作数栈,栈顶是value2,value2下面的是value1。

…, result

这个表示结果入栈,放在操作数栈顶部。
结合起来看就是,value1、value2出栈,通过计算后将result入栈。操作数栈里其他的内容使用 ... 表示(不会对指令执行产生影响)

Values of types long and double are represented by a single entry on the operand stack.

long类型、double类型在 form 图标里用单个方框表示

In the First Edition of The Java® Virtual Machine Specification, values on the operand stack of types long and double were each represented in the stack diagram by two entries.

第一个版本的JVMS,long和double使用两个方框表示

次要部分

image.png
这一部分就没那么重要,大致看看就行了~