ava虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
对于大部分与数据类型相关的字节码指令,他们的操作码助记符中都有特殊的字符来表明专门为哪种类型服务:
- i代表对int类型的操作
- l代long
- s代表short
- b代表byte
- c代表char
- f代表float
- d代表double
- a代表reference
也有一些指令的助记符中没有明确地指明操作类型的字母,例如arraylength指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。
还有另外一些指令,例如无条件跳转指令goto则是与数据类型无关的。
加载和存储指令
加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传输。
1. 将一个局部变量加载到操作栈的指令 |
---|
iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n> |
2. 将一个数值从操作数栈存储到局部变量表的指令 |
---|
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n> |
3. 将一个常量加载到操作数栈的指令 |
---|
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d> |
4. 扩充局部变量表的访问索引的指令 |
---|
wide |
上述所列举的指令助记符中,有一部分是以尖括号结尾的(例如iload_<n>),这些指令助记符实际上是代表了一组指令(例如iload_<n>,它代表了iload_0、iload_1、iload_2和iload_3这几条指令)。这几条指令都是某个带有一个操作数的通用指令(例如iload)的特殊指令,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的操作,但操作数都是指令隐含的。除此之外,他们的语义与原生的通用指令完全一致(例如iload_0的语义与操作数为0时的iload指令语义完全一致)。
运算指令
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。大体运算指令可以分为两种:对整型数据进行运算的指令与对浮点型数据进行运算的指令,无论是那种算术指令,都是使用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
类型转换指令
类型转换指令可以将两种Java虚拟机数值类型进行相互转换,这些转换操作一般用于实现用户代码的显示类型转换操作,或者用来处理Java虚拟机字节码指令集中指令非完全独立的问题。
Java虚拟机直接支持(“直接支持”意味着转换时无需显示的转换指令)以下数值的宽化类型转换(Widening Numeric Conversions,小范围类型向大范围类型的安全转换):
- int类型到long、float或者double类型
- long类型到float、double类型
- float类型到double类型
窄化类型转换(Narrowing Numeric Conversions)指令包括有: i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
对象创建与操作
虽然类实例和数组都是对象,但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令:
- 创建类实例的指令:new
- 创建数组实例的指令:newarray,anewarray,multianewarray
- 访问类字段(static字段,或称为类变量)和实例字段(非static字段,或者称为实例变量)的指令:getstatic、putstatic、getfield、putfield
- 把一个数组元素加载到操作数栈的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
- 将一个操作数栈的值存储到数组元素的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore
- 取数组长度的指令:arraylength
- 检查类实例类型的指令:instanceof、checkcast
操作数栈管理指令
Java虚拟机提供了一些用于直接操作操作数栈的指令,包括:pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2和swap。
控制转移指令
控制转移指令可以让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
方法调用和返回指令
以下四条指令用于方法调用:
指令 | 作用 |
---|---|
invokevirtual | 用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式 |
invokeinterface | 用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用 |
invokespecial | 用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法 |
invokestatic | 用于调用类方法(static方法) |
抛出异常
在程序中显示抛出异常的操作会由athrow指令实现,除了这种情况,还有别的异常会在其他Java虚拟机指令检测到异常情况时由虚拟机自动抛出。
同步
monitorenter和monitorexit