8.字节码指令

来源:互联网 发布:天猫宝贝标题优化 编辑:程序博客网 时间:2024/06/01 23:51
1.简介
Java虚拟机的指令由一个字节长度的、 代表着某种特定操作含义的数字(称为操作码, Opcode) 以及跟随其后的零至多个代表此操作所需参数(称为操作数, Operands) 而构成。Java虚拟机采用面向操作数栈,大多数的指令都不包含操作数, 只有一个操作码。
字节码指令集是一种具有鲜明特点、 优劣势都很突出的指令集架构, 限制了Java虚拟机操作码的长度为一个字节(即0~255) , 指令集的操作码总数不可能超过256条; Class文件格式放弃了编译后代码的操作数长度对齐, 虚拟机处理超过一个字节数据的时候,需要在运行时从字节中重建出具体数据的结构。
1.1 字节码与数据类型
在Java虚拟机的指令集中, 大多数的指令都包含了其操作所对应的数据类型信息。
对于大部分与数据类型相关的字节码指令, 它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务: i代表对int类型的数据操作, l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。

编译器会在编译期或运行期将byte和short类型的数据带符号扩展(Sign-Extend) 为相应的int类型数据, 将boolean和char类型数据零位扩展(Zero-Extend) 为相应的int类型数据。 与之类似, 在处理boolean、byte、 short和char类型的数组时, 也会转换为使用对应的int类型的字节码指令来处理。 因此, 大多数对于boolean、 byte、 short和char类型数据的操作, 实际上都是使用相应的int类型作为运算类型(ComputationalType) 。
2.字节码指令分类
2.1加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
A.将一个局部变量加载到操作栈: iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、 dload、dload_<n>、aload、aload_<n>。
B.将一个数值从操作数栈存储到局部变量表: istore、istore_<n>、lstore、lstore_<n>、 fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。
C.将一个常量加载到操作数栈: bipush、sipush、ldc、 ldc_w、 ldc2_w、 aconst_null、 iconst_m1、 iconst_<i>、 lconst_<l>、 fconst_<f>、 dconst_<d>。
D.扩充局部变量表的访问索引的指令: wide。
2.2运算指令
运算或算术指令用于对两个操作数栈上的值进行某种特定运算, 并把结果重新存入到操作栈顶。 大体上算术指令可以分为两种: 对整型数据进行运算的指令与对浮点型数据进行运算的指令,没有直接支持byte、 short、 char和boolean类型的算术指令, 对于这类数据的运算, 应使用操作int类型的指令代替。
A.加法指令: iadd、 ladd、 fadd、 dadd。
B.减法指令: isub、 lsub、 fsub、 dsub。
C.乘法指令: imul、 lmul、 fmul、 dmul。
D.除法指令: idiv、 ldiv、 fdiv、 ddiv。
E.求余指令: irem、 lrem、 frem、 drem。
F.取反指令: ineg、 lneg、 fneg、 dneg。
G.位移指令: ishl、 ishr、 iushr、 lshl、 lshr、 lushr。
H.按位或指令: ior、 lor。
I.按位与指令: iand、 land。
J.按位异或指令: ixor、 lxor。
K.局部变量自增指令: iinc。
L.比较指令: dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp。
Java虚拟机规范没有明确定义过整型数据溢出的具体运算结果, 仅规定了在处理整型数据时, 只有除法指令(idiv和ldiv) 以及求余指令(irem和lrem) 中当出现除数为零时会导致虚拟机抛出ArithmeticException异常, 其余任何整型数运算场景都不应该抛出运行时异常。
Java虚拟机在处理浮点数运算时, 不会抛出任何运行时异常。 当一个操作产生溢出时, 将会
使用有符号的无穷大来表示, 如果某个操作结果没有明确的数学定义的话, 将会使用NaN值来表示。 所有使用NaN值作为操作数的算术操作, 结果都会返回NaN。
2.3类型转换指令
类型转换指令可以将两种不同的数值类型进行相互转换, 这些转换操作一般用于实现用户代码中的显式类型转换操作, 或者用来处理本节开篇所提到的字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
Java虚拟机直接支持(即转换时无需显式的转换指令) 以下数值类型的宽化类型转换:
int类型到long、 float或者double类型。
long类型到float、 double类型。
float类型到double类型。
处理窄化类型转换(Narrowing Numeric Conversions) 时, 必须显式地使用转换指令来完成, 这些转换指令包括: i2b、 i2c、 i2s、 l2i、 f2i、 f2l、 d2i、 d2l和d2f。
2.4对象创建与访问指令
类实例和数组都是对象, 但Java虚拟机对类实例和数组的创建与操作使用了不同的字节码指令。对象创建后, 就可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素。
A.创建类实例的指令: new。
B.创建数组的指令: newarray、 anewarray、 multianewarray。
C.访问类字段(static字段, 或者称为类变量) 和实例字段(非static字段, 或者称为实例变量) 的指令:getfield、 putfield、 getstatic、 putstatic。
D.把一个数组元素加载到操作数栈的指令: baload、 caload、 saload、 iaload、 laload、 faload、 daload、aaload。
E.将一个操作数栈的值存储到数组元素中的指令: bastore、 castore、 sastore、 iastore、 fastore、 dastore、aastore。
F.取数组长度的指令: arraylength。
G.检查类实例类型的指令: instanceof、 checkcast。
2.5操作数栈管理指令
Java虚拟机提供了一些用于直接操作操作数栈的指令.
A.将操作数栈的栈顶一个或两个元素出栈: pop、 pop2。
B.复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶: dup、 dup2、 dup_x1、 dup2_x1、 dup_x2、dup2_x2。
C.将栈最顶端的两个数值互换: swap。
2.6控制转移指令
控制转移指令可以让Java虚拟机有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序, 从概念模型上理解, 可以认为控制转移指令就是在有条件或无条件地修改PC寄存器的值。
A.条件分支: ifeq、 iflt、 ifle、 ifne、 ifgt、 ifge、 ifnull、 ifnonnull、 if_icmpeq、 if_icmpne、
if_icmplt、 if_icmpgt、 if_icmple、 if_icmpge、 if_acmpeq和if_acmpne。
B.复合条件分支: tableswitch、 lookupswitch。
C.无条件分支: goto、 goto_w、 jsr、 jsr_w、 ret。
2.7方法调用和返回指令
A.invokevirtual指令用于调用对象的实例方法, 根据对象的实际类型进行分派(虚方法分派) , 这也是Java语言中最常见的方法分派方式。
B.invokeinterface指令用于调用接口方法, 它会在运行时搜索一个实现了这个接口方法的对象, 找出适合的方法进行调用。
C.invokespecial指令用于调用一些需要特殊处理的实例方法, 包括实例初始化方法、 私有方法和父类方法。
D.invokestatic指令用于调用类方法(static方法) 。
E.invokedynamic指令用于在运行时动态解析出调用点限定符所引用的方法, 并执行该方法。
F.方法返回指令是根据返回值的类型区分的, 包括ireturn(当返回值是boolean、 byte、 char、 short和int类型时使用) 、 lreturn、 freturn、 dreturn和areturn, 另外还有一条return指令供声明为void的方法、 实例初始化方法以及类和接口的类初始化方法使用。
2.8异常处理指令
在Java程序中显式抛出异常的操作(throw语句) 都由athrow指令来实现, 除了用throw语句显式抛出异常情况之外, Java虚拟机规范还规定了许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出。
Java虚拟机处理异常(catch语句) 不是由字节码指令来实现的, 而是采用异常表来完成的。
2.9同步指令
Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步, 这两种同步结构都是使用管程(Monitor) 来支持的。
方法级的同步是隐式的, 即无须通过字节码指令来控制, 它实现在方法调用和返回操作之中。
同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的, Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义, 正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持。

原创粉丝点击