《Java虚拟机规范》-字节码指令集

时间:2020-12-05 17:09:38
 

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

《Java虚拟机规范》-字节码指令集

上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如 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

《Java虚拟机规范》-字节码指令集

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.控制转移指令

《Java虚拟机规范》-字节码指令集

8.方法调用和返回指令

方法调用指令:

  • invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派)
  • invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用
  • invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法)、私有方法和父类方法
  • invokestatic 指令用于调用类方法(static 方法)

方法返回指令: 根据返回值的类型区分,包括有 ireturn(当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 和 areturn,另外还有一条 return 指令供声明为 void 的方法、实例初始化方法、类和接口的类初始化方法使用

9.抛出异常指令

显示抛出异常:athrow,其他的异常会在其他Java虚拟机指令检测到异常情况时由Java虚拟机自动抛出

10.同步