原创

Java字节码指令

1、加载与存储指令

加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传输:

将一个局部变量加载到操作栈的指令包括有:

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

访问对象的字段或数组元素的指令也同样会与操作数栈传输数据。

上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如 iload*),这些指令助记符实际上是代表了一组指令(例如 iload*,它代表了 iload_0、iload_1、iload_2 和 iload_3 这几条指令)。

这几组指令都是某个带有一个操作数的通用指令(例如 iload)的特殊形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都是在指令中隐含的。

除此之外,他们的语义与原生的通用指令完全一致(例如 iload_0 的语义与操作数为 0 时的 iload 指令语义完全一致)。

在尖括号之间的字母制定了指令隐含操作数的数据类型,<i> 代表是 int 形数据,代表 long 型,代表 float 型,代表 double 型。在操作 byte、char 和 short 类型数据时,也用 int 类型表示。 这种指令表示方法,在整个《Java 虚拟机规范》之中都是通用的。

2、运算指令

算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。

大体上运算指令可以分为两种:对整型数据进行运算的指令与对浮点型数据进行运算的指令,无论是那种算术指令,都是使用 Java 虚拟机的数字类型的。

数据没有直接支持 byte、short、char 和 boolean 类型的算术指令,对于这些数据的运算,都是使用操作 int 类型的指令。

整数与浮点数的算术指令在溢出和被零除的时候也有各自不同的行为,所有的算术指令包括:

加法指令:

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

3、类型转换指令

窄化类型转换(Narrowing Numeric Conversions)指令包括有:

i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l 和 d2f。

窄化类型转换可能会导致转换结果产生不同的正负号、不同的数量级,转换过程很可能会导致数值丢失精度。

4、对象创建与操作指令

虽然类实例和数组都是对象,但 Java 虚拟机对类实例和数组的创建与操作使用了不同的字节码指令:

创建类实例的指令:

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、checkcas

5、操作数栈管理指令

Java 虚拟机提供了一些用于直接操作操作数栈的指令,包括:

pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2 和 swap。

6、控制转移指令

控制转移指令可以让 Java 虚拟机有条件或无条件地从指定指令而不是控制转移指令的下一条指令继续执行程序。

控制转移指令包括有:

条件分支:

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

7、方法调用和返回指令

以下四条指令用于方法调用:

invokevirtual

指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),

这也是 Java 语言中最常见的方法分派方式。

invokeinterface

指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。

invokespecial

指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。

invokestatic

指令用于调用类方法(static 方法)。

而方法返回指令则是根据返回值的类型区分的, 包括有 ireturn(当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 和 areturn,

另外还有一条 return 指令供声明为 void 的方法、实例初始化方法、类和接口的类初始化方法使用。

8、抛出异常

在程序中显式抛出异常的操作会由 athrow 指令实现,

除了这种情况,还有别的异常会在其它 Java 虚拟机指令检测到异常状况时由虚拟机自动抛出。

9、同步指令

同步一段指令集序列通常是由 Java 语言中的 synchronized 块来表示的,

Java 虚拟机的指令集中有 monitorentermonitorexit 两条指令来支持 synchronized 关键字的语义,正确实现 synchronized 关键字需要编译器与 Java 虚拟机两者协作支持。

正文到此结束