6.4.1 字节码与数据类型

编译器会在编译期或运行期将byte和short类型的数据带符号扩展(Sign-Extend)为相应的int类型数据,将boolean和char类型数据零位扩展(Zero-Extend)为相应的int类型数据。因此,大多数对于boolean、byte、short和char类型数据的操作,实际上都是使用相应的对int类型作为运算类型(Computational Type)来进行的

Java虚拟机指令集所支持的数据类型

opcodebyteshortintlongfloatdoublecharreference
Tipushbipushsipush
Tconsticonstlconstfconstdconstaconst
Tloadiloadlloadfloaddloadaload
Tstoreistorelstorefstoredstoreastore
Tinciinc
Taloadbaloadsaloadialoadlaloadfaloaddaloadcaloadaaload
Tastorebastoresastoreiastorelastorefastoredastorecastoreaastore
Taddiaddladdfadddadd
Tsubisublsubfsubdsub
Tmulimullmulfmuldmul
Tdividivldivfdivddiv
Tremiremlremfremdrem
Tnegineglnegfnegdneg
Tshlishllshl
Tshrishrlshr
Tushriushrlushr
Tandiandland
Toriorlor
Txorixorlxor
i2Ti2bi2si2li2fi2d
l2Tl2il2fl2d
f2Tf2if2lf2d
d2Td2id2ld2f
Tcmplcmp
Tcmp1fcmpldcmpl
Tcmpgfcmpgdcmpg
if_TcmpOPif_icmpOPif_acmpOP
Treturnireturnlreturnfreturndreturnareturn

6.4.2 加载和存储指令

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

将一个局部变量加载到操作栈:iload、iload_、lload、lload_、fload、fload_、dload、dload_、aload、aload_

将一个数值从操作数栈存储到局部变量表:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_

将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_、lconst_、fconst_、dconst_

扩充局部变量表的访问索引的指令:wide

6.4.3 运算指令

对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶.遵循IEEE754规范

加法指令: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

6.4.4 类型转换指令

窄化类型转换(Narrowing Numeric Conversion)必须显式使用转换指令完成,指令包括i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f

int或long类型窄化为T:丢弃除最低位N(T的数据类型长度)字节以外的内容,可能导致转换结果与输入值有不同的正负号

浮点值窄化转换为T(T限于int或long):

  • 如果浮点值是NaN,那转换结果就是int或long类型的0
  • 如果浮点值不是无穷大的话,浮点值使用IEEE 754的向零舍入模式取整,获得整数值v。如果v在目标类型T(int或long)的表示范围之类,那转换结果就是v;否则,将根据v的符号,转换为T所能表示的最大或者最小正数

6.4.5 对象创建与访问指令

创建类实例的指令: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.4.6 操作数栈管理指令

将操作数栈的栈顶一个或两个元素出栈:pop、pop2

复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2

将栈最顶端的两个数值互换:swap

6.4.7 控制转移指令

让Java虚拟机有条件或无条件地从指定位置指令(而不是控制转移指令)的下一条指令继续执行程序,从概念模型上理解,可以认为控制指令就是在有条件或无条件地修改PC寄存器的值

条件分支: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

6.4.8 方法调用和返回指令

方法调用

  • invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式
  • invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用
  • invokespecial指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法
  • invokestatic指令:用于调用类静态方法(static方法)
  • invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面四条调用指令的分派逻辑都固化在Java虚拟机内部,用户无法改变,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的

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

6.4.9 异常处理指令

由athrow指令实现,许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出.采用异常表完成

6.4.10 同步指令

使用管程(Monitor,更常见的是直接将它称为“锁”)实现方法级的同步:检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程同步一段指令集序列:monitorenter和monitorexit两条指令