Java虚拟机指令:一字节长度的操作码和其后跟随的零至多个操作数构成
忽略异常,Java 虚拟机的解释器使用下面的伪代码的循环即可有效工作:
do {
自动计算 PC 寄存器以及从 PC 寄存器的位置取出操作码;
if (存在操作数) 取出操作数;
执行操作码所定义的操作
} while (处理下一次循环);
操作数个数取决于操作码,操作数长度如果超过一个字节,按照Big-Endian顺序存储-即高位在前的字节序
限定操作码长度为一个字节-直接限制了指令集的数量
参数未对齐-虚拟机处理超过一个字节的操作数时,不得不在运行时从字节中重建出具体数据的结构,会损失一些性能。
1.数据类型与Java虚拟机
在 Java 虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。
如:
iload 指令用于从局部变量表中加载 int 型的数据到操作数栈中
fload 指令加载的则是float 类型的数据
操作码助记符:
i 代表对 int 类型的数据操作,
l 代表 long,
s 代表 short,
b 代表 byte,
c 代表 char,
f 代表 float,
d 代表 double,
a 代表 reference
也有一些指令的助记符中没有明确的指明操作类型的字母,例如 arraylength 指令,它没有代表数据类型的特殊字符,但 操作数永远只能是一个数组类型的对象
还有另外一些指令,例如无条件跳转指令 goto 则是与数据类型无关的
由于指令集有限,对于特定的操作只提供有限的类型相关指令去支持它
大部分的指令都没有支持整数类型 byte、char 和 short,甚至没有任何指令支持 boolean 类型。编译器会在编译期或运行期会将 byte 和 short 类型的数据带符号扩展(Sign-Extend)为相应的 int 类型数据,将 boolean 和 char 类型数据零位扩展(Zero-Extend)为相应的 int 类型数据。与之类似的,在处理 boolean、byte、short 和char 类型的数组时,也会转换为使用对应的 int 类型的字节码指令来处理。因此,大多数对于boolean、byte、short 和 char 类型数据的操作,实际上都是使用相应的对 int 类型作为运算类型(Computational Type)
2.加载和存储指令
用于将数据从栈帧的局部变量表和操作数栈之间来回传输。
加载数据至操作栈-load、从操作数栈存储至局部变量表-store、加载常量至操作数栈-push
上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如 iload< n >),这些指令助记符实际上是代表了一组指令(例如 iload< n >,它代表了 iload_ 0、iload_ 1、iload_ 2 和iload_ 3 这几条指令)。这几组指令都是某个带有一个操作数的通用指令(例如 iload)的特殊形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都是在指令中隐含的。除此之外,他们的语义与原生的通用指令完全一致(例如 iload_0 的语义与操作数为 0 时的 iload 指令语义完全一致)。在尖括号之间的字母制定了指令隐含操作数的数类型,< i >代表是int型数据、< l >代表long型、< f >代表float型、< d >代表double型,在操作byte、char和short类型数据时,用int类型表示。
3.运算指令
用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
大体上运算指令可以分为两种:对整型数据进行运算的指令和对浮点型数据进行运算的指令
加add、减sub、乘mul、除div、求余rem、取反neg、位移shl/shr、按位或or、按位与and、按位异或xor、局部变量自增iinc、比较指令cmpg/cmpl/cmp
4.类型转换指令
用于实现用户代码的显式类型转换操作。
Java虚拟机直接支持宽化类型转换。如:int 类型到 long、float 或者 double 类型等
窄化类型转换指令:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f
5.对象创建于操作
类实例和数组的创建于操作使用不同的指令(数组元素入操作数栈aload、操作数栈值存储到数组元素astore)
- 创建类实例的指令:new
- 创建数组的指令:newarray,anewarray,multianewarray
- 访问类字段(static 字段,或者称为类变量)和实例字段(非 static 字段,或者成为实例变量)的指令:getfield、putfield、getstatic、putstatic
- 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
- 将一个操作数栈的值储存到数组元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
- 取数组长度的指令:arraylength
- 检查类实例类型的指令:instanceof、checkcast
6.操作数栈管理指令
用于直接操作操作数栈
包括:pop、pop2、dup、dup2、dupx1、dup2x1、dupx2、dup2x2 和 swap
7.控制转移指令
8.方法调用和返回指令
方法调用指令:
- invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派)
- invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用
- invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法)、私有方法和父类方法
- invokestatic 指令用于调用类方法(static 方法)
方法返回指令: 根据返回值的类型区分,包括有 ireturn(当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 和 areturn,另外还有一条 return 指令供声明为 void 的方法、实例初始化方法、类和接口的类初始化方法使用
9.抛出异常指令
显示抛出异常:athrow,其他的异常会在其他Java虚拟机指令检测到异常情况时由Java虚拟机自动抛出