《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》6.4节
长度:1个字节
总数:<=256
面向:操作数栈,而非寄存器
对齐:操作数长度不对齐
操作码助记符:i代表int,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference
arraylength指令,无操作码助记符,但操作数永远只能数组
虚拟机指令集支持的数据类型:
大部分指令不支持byte、char、short(转为int处理)
所有指令不支持boolean(转为int处理)
可按用途分为9类:
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
操作数栈和局部变量表主要由加载和存储指令操作
2. 运算指令
加法指令: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虚拟机规范仅规定,处理整型数据时只有除法指令(idiv、ldiv)及求余指令(irem、lrem)中除数为零时虚拟机抛出ArithmeticException异常,其余任何整型数运算都不应抛出运行时异常
3. 类型转换指令
i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f
处理窄化类型转换(NarrowingNumericConversions)必须显式使用转换指令
数据类型窄化转换可能会发生上限溢出、下限溢出、精度丢失等情况
Java虚拟机规范规定数值类型窄化转换指令永远不可能导致虚拟机抛出运行时异常
4. 对象创建与访问指令
创建类实例的指令: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
5. 操作数栈管理指令
将操作数栈的栈顶一个或两个元素出栈:pop、pop2
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
将栈最顶端的两个数值互换:swap
6. 控制转移指令
从概念模型理解,有条件或无条件的修改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
7. 方法调用和返回指令
方法调用指令与数据类型无关
invokevirtual,用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),Java语言中最常见的方法分派方式
invokeinterface,用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用
invokespecial,用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法
invokestatic,用于调用类方法(static方法)
invokedynamic,用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法
前面4条调用指令分派逻辑固化在Java虚拟机内部,invokedynamic指令分派逻辑是由用户所设定的引导方法决定
方法返回指令是根据返回值类型区分
包括,ireturn(返回值是boolean、byte、char、short和int类型)、lreturn、freturn、dreturn、areturn
return,供声明为void的方法、实例初始化方法及类和接口的类初始化方法使用
8. 异常处理指令
显示抛出异常的操作:athrow
9. 同步指令
monitorenter、monitorexit
Java虚拟机支持方法级同步和方法内部一段指令序列同步,两种同步结构都使用
管程(Monitor)支持实现
方法级同步(
方法同步的底层实现原理):
隐式的,即无须通过字节码指令控制,实现在方法调用和返回操作之中
虚拟机可通过方法常量池中方法表结构的ACC_SYNCHRONIZED访问标志得知方法是否被声明为同步方法
方法调用时,调用指令检查ACC_SYNCHRONIZED访问标志是否被设置,若是,则执行线程要求先成功持有管程,然后才执行方法,方法完成(包括正常、非正常完成)时释放管程
方法执行期间,执行线程持有了管程,其他线程无法再获取同一个管程
若同步方法执行期间抛出异常,且方法内部无法处理此异常,则同步方法持有的管程在异常抛出同步方法时自动释放
同步一段指令集序列(
synchronized语句块的底层实现原理):
由Java语言synchronized语句块表示
指令集中monitorenter、monitorexit支持synchronized关键字语义
正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持
编译器必须确保,无论方法以何种方式(正常结束、异常结束)完成,方法中调用的每条monitorenter指令都必须执行其对应的monitorexit指令
为保证方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器自动产生一个异常处理器,该异常处理器声明可处理所有的常,其目的是用执行monitorexit指令
管程(Monitor)的概念: