简介
- Java虚拟机采用面向操作数栈而不是面向寄存器的架构
- Java虚拟机的指令由操作码+操作数组成
- 操作码为一个字节,0-255,指令不能超过256条,目前已有200多条,能合并就合并
- class文件放弃了操作数的长度对齐,
- 虚拟机在处理那些超过一个字节的数据时, 需要在运行时从字节中重建出具体数据的结构。
- 将一个16位长度的无符号整数使用两个无符号字节存储起来(假设将它们命名为byte1和byte2), 需要进行:(byte1 << 8) | byte2
- 优势是可以可以省略掉大量的填充和间隔符号
- 虚拟机在处理那些超过一个字节的数据时, 需要在运行时从字节中重建出具体数据的结构。
- 大多数指令都包含其操作所对应的数据类型信息。但是不可能每种数据类型都有全部的指令,因为指令最多有256条。
加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈 之间来回传输, 这类指令包括
- 将一个局部变量加载到操作栈: 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_
运算指令
算术指令用于对两个操作数栈上的值进行某种特定运算, 并把结果重新存入到操作栈顶。
- 加法指令: 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
操作数栈管理指令
- 将操作数栈的栈顶一个或两个元素出栈: pop、 pop2
- 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶: dup、 dup2、 dup_x1、dup2_x1、 dup_x2、 dup2_x2
- 将栈最顶端的两个数值互换: swap
控制转移指令
控制转移指令可以让Java虚拟机有条件或无条件地从指定位置指令(而不是控制转移指令) 的下一条指令继续执行程序, 从概念模型上理解, 可以认为控制指令就是在有条件或无条件地修改PC寄存器的值。 控制转移指令包括:
- 无条件分支: goto、 goto_w、 jsr、 jsr_w、 ret
- 条件分支: 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
方法调用和返回指令
方法调用(分派、 执行过程):
- invokevirtual指令: 用于调用对象的实例方法, 根据对象的实际类型进行分派(虚方法分派) ,
这也是Java语言中最常见的方法分派方式。
·invokeinterface指令: 用于调用接口方法, 它会在运行时搜索一个实现了这个接口方法的对象, 找
出适合的方法进行调用。
·invokespecial指令: 用于调用一些需要特殊处理的实例方法, 包括实例初始化方法、 私有方法和
父类方法。
·invokestatic指令: 用于调用类静态方法(static方法) 。
·invokedynamic指令: 用于在运行时动态解析出调用点限定符所引用的方法。 并执行该方法。 前面
四条调用指令的分派逻辑都固化在Java虚拟机内部, 用户无法改变, 而invokedynamic指令的分派逻辑
是由用户所设定的引导方法决定的。
方法调用指令与数据类型无关, 而方法返回指令是根据返回值的类型区分的, 包括ireturn(当返
回值是boolean、 byte、 char、 short和int类型时使用) 、 lreturn、 freturn、 dreturn和areturn, 另外还有一
条return指令供声明为void的方法、 实例初始化方法、 类和接口的类初始化方法使用。
类型转换指令
安全的转化无需使用jvm转化指令
- int类型到long、 float或者double类型
- long类型到float、 double类型
- float类型到double类型
相反的,大转小需要指令i2b、 i2c、 i2s、 l2i、 f2i、 f2l、 d2i、 d2l和d2f
对象创建与访问指令
虽然类实例和数组都是对象, 但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、 checkcast+
异常处理指令
- 在Java程序中显式抛出异常的操作(throw语句) 都由athrow指令来实现。
- 许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出。
- 例如前面介绍整数运算中, 当除数为零时, 虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常。
- 处理异常(catch语句) 以前使用jsr和ret指令, 现在用异常表。
同步指令
- 方法级的同步是隐式的, 不使用字节码指令。JVM从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为同步方法。 当方法调用时, 调用指令将会检查方法是否是同步方法,如果是同步方法, 执行线程就要先成功持有Monitor, 然后才能执行方法, 最后当方法完成(无论是正常完成还是非正常完成) 时释放Monitor。
- 使用synchronized修饰一段代码块,在编译为字节码文件时,会在同步代码块前后加入monitorenter和monitorexit指令,编译器确保无论这个方法是正常结束还是异常结束,每条monitorenter指令都必须有其对应的monitorexit指令。