Java字节码和指令集
Java 字节码是Java虚拟机执行的一种指令格式。大多数操作码都是一个字节长,而有些操作需要参数,导致了有一些多字节的操作码。而且并不是所有可能的256个操作码都被使用;其中有51个操作码被保留做将来使用。除此之外,Sun 公司额外保留了3个代码永久不使用。
指令
每一个字节,有256个可能的代码值(2^8=256),因此一个字节的操作码最多可能有256种不同的操作。其中,0x00、0xFE、0xCA、0xFF被指定保留。例如0xCA作为一个Java调试器的中断指令而从未被语言使用。相似地,0xFE和0xFF也未被语言使用。
指令可以基本分为以下几类:
- 存储指令 (例如:aload_0, istore)
- 算术与逻辑指令 (例如: ladd, fcmpl)
- 类型转换指令 (例如:i2b, d2i)
- 对象创建与操作指令 (例如:new, putfield)
- 堆栈操作指令 (例如:swap, dup2)
- 控制转移指令 (例如:ifeq, goto)
- 方法调用与返回指令 (例如:invokespecial, areturn)
除此之外,还有一些更特殊的指令,作为异常抛出或同步等作用。
大多数的指令有前缀和(或)后缀来表明其操作数的类型。如下表
| 前/后缀 | 操作数类型 |
|---|---|
| i | 整数 |
| l | 长整数 |
| s | 短整数 |
| b | 字节 |
| c | 字符 |
| f | 单精度浮点数 |
| d | 双精度浮点数 |
| z | 布尔值 |
| a | 引用 |
例如,“iadd”指令将两个整数相加;而”dadd”指令将两个double浮点数相加。 此外,“const”、 “load”、 “store”等命令还会使用”_n”后缀,其中 “load”和”store”命令中的n可以为0到3之间的整数;而”const”命令中的n由类型指定。 const”指令把一个指定类型的值放入堆栈。例如”iconst_5”指令将一个整数5放入堆栈;而”dconst_1”将一个双精度浮点数1放入堆栈。 此外还有”aconst_null”指令,放入一个null进堆栈。而对于”load” “store”指令中的n,指定了变量表中的存储位置。“aload_0”指令把在变量0中的对象(通常是”this”对象)放入堆栈,“istore_1”指令把栈顶的一个整数放入变量1.对于更高的变量,后缀将被去除,而这条指令将需要操作数。
指令集
| 助记词 | 说明 |
|---|---|
| nop | None |
| aconst_null | 将null推送至栈顶 |
| iconst_m1 | 将int型-1推送至栈顶 |
| iconst_0 | 将int型0推送至栈顶 |
| iconst_1 | 将int型1推送至栈顶 |
| iconst_2 | 将int型2推送至栈顶 |
| iconst_3 | 将int型3推送至栈顶 |
| iconst_4 | 将int型4推送至栈顶 |
| iconst_5 | 将int型5推送至栈顶 |
| lconst_0 | 将long型0推送至栈顶 |
| lconst_1 | 将long型1推送至栈顶 |
| fconst_0 | 将float型0推送至栈顶 |
| fconst_1 | 将float型1推送至栈顶 |
| fconst_2 | 将float型2推送至栈顶 |
| dconst_0 | 将double型0推送至栈顶 |
| dconst_1 | 将double型1推送至栈顶 |
| bipush | 将单字节的常量值(-128~127)推送至栈顶 |
| sipush | 将一个短整型常量(-32768~32767)推送至栈顶 |
| ldc | 将int,float或String型常量值从常量池中推送至栈顶 |
| ldc_w | 将int,float或String型常量值从常量池中推送至栈顶(宽索引) |
| ldc2_w | 将long或double型常量值从常量池中推送至栈顶(宽索引) |
| iload | 将指定的int型本地变量推送至栈顶 |
| lload | 将指定的long型本地变量推送至栈顶 |
| fload | 将指定的float型本地变量推送至栈顶 |
| dload | 将指定的double型本地变量推送至栈顶 |
| aload | 将指定的引用类型本地变量推送至栈顶 |
| iload_0 | 将第一个int型本地变量推送至栈顶 |
| iload_1 | 将第二个int型本地变量推送至栈顶 |
| iload_2 | 将第三个int型本地变量推送至栈顶 |
| iload_3 | 将第四个int型本地变量推送至栈顶 |
| lload_0 | 将第一个long型本地变量推送至栈顶 |
| lload_1 | 将第二个long型本地变量推送至栈顶 |
| lload_2 | 将第三个long型本地变量推送至栈顶 |
| lload_3 | 将第四个long型本地变量推送至栈顶 |
| fload_0 | 将第一个float型本地变量推送至栈顶 |
| fload_1 | 将第二个float型本地变量推送至栈顶 |
| fload_2 | 将第三个float型本地变量推送至栈顶 |
| fload_3 | 将第四个float型本地变量推送至栈顶 |
| dload_0 | 将第一个double型本地变量推送至栈顶 |
| dload_1 | 将第二个double型本地变量推送至栈顶 |
| dload_2 | 将第三个double型本地变量推送至栈顶 |
| dload_3 | 将第四个double型本地变量推送至栈顶 |
| aload_0 | 将第一个引用类型本地变量推送至栈顶 |
| aload_1 | 将第二个引用类型本地变量推送至栈顶 |
| aload_2 | 将第三个引用类型本地变量推送至栈顶 |
| aload_3 | 将第四个引用类型本地变量推送至栈顶 |
| iaload | 将int型数组指定索引的值推送至栈顶 |
| laload | 将long型数组指定索引的值推送至栈顶 |
| faload | 将float型数组指定索引的值推送至栈顶 |
| daload | 将double型数组指定索引的值推送至栈顶 |
| aaload | 将引用类型数组指定索引的值推送至栈顶 |
| baload | 将boolean或byte型数组指定索引的值推送至栈顶 |
| caload | 将char型数组指定索引的值推送至栈顶 |
| saload | 将short型数组指定索引的值推送至栈顶 |
| istore | 将栈顶int型数值存入指定本地变量 |
| lstore | 将栈顶long型数值存入指定本地变量 |
| fstore | 将栈顶float型数值存入指定本地变量 |
| dstore | 将栈顶double型数值存入指定本地变量 |
| astore | 将栈顶引用类型数值存入指定本地变量 |
| istore_0 | 将栈顶int型数值存入第一个本地变量 |
| istore_1 | 将栈顶int型数值存入第二个本地变量 |
| istore_2 | 将栈顶int型数值存入第三个本地变量 |
| istore_3 | 将栈顶int型数值存入第四个本地变量 |
| lstore_0 | 将栈顶long型数值存入第一个本地变量 |
| lstore_1 | 将栈顶long型数值存入第二个本地变量 |
| lstore_2 | 将栈顶long型数值存入第三个本地变量 |
| lstore_3 | 将栈顶long型数值存入第四个本地变量 |
| fstore_0 | 将栈顶float型数值存入第一个本地变量 |
| fstore_1 | 将栈顶float型数值存入第二个本地变量 |
| fstore_2 | 将栈顶float型数值存入第三个本地变量 |
| fstore_3 | 将栈顶float型数值存入第四个本地变量 |
| dstore_0 | 将栈顶double型数值存入第一个本地变量 |
| dstore_1 | 将栈顶double型数值存入第二个本地变量 |
| dstore_2 | 将栈顶double型数值存入第三个本地变量 |
| dstore_3 | 将栈顶double型数值存入第四个本地变量 |
| astore_0 | 将栈顶引用型数值存入第一个本地变量 |
| astore_1 | 将栈顶引用型数值存入第二个本地变量 |
| astore_2 | 将栈顶引用型数值存入第三个本地变量 |
| astore_3 | 将栈顶引用型数值存入第四个本地变量 |
| iastore | 将栈顶int型数值存入指定数组的指定索引位置 |
| lastore | 将栈顶long型数值存入指定数组的指定索引位置 |
| fastore | 将栈顶float型数值存入指定数组的指定索引位置 |
| dastore | 将栈顶double型数值存入指定数组的指定索引位置 |
| aastore | 将栈顶引用型数值存入指定数组的指定索引位置 |
| bastore | 将栈顶boolean或byte型数值存入指定数组的指定索引位置 |
| castore | 将栈顶char型数值存入指定数组的指定索引位置 |
| sastore | 将栈顶short型数值存入指定数组的指定索引位置 |
| pop | 将栈顶数值弹出(数值不能是long或double类型的) |
| pop2 | 将栈顶的一个(对于非long或double类型)或两个数值(对于非long或double的其他类型)弹出 |
| dup | 复制栈顶数值并将复制值压入栈顶 |
| dup_x1 复制栈顶数值并将两个复制值压入栈顶 | |
| dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶 | |
| dup2 | 复制栈顶一个(对于long或double类型)或两个(对于非long或double的其他类型)数值并将复制值压入栈顶 |
| dup2_x1 | dup_x1指令的双倍版本 |
| dup2_x2 | dup_x2指令的双倍版本 |
| swap | 将栈顶最顶端的两个数值互换(数值不能是long或double类型) |
| iadd | 将栈顶两int型数值相加并将结果压入栈顶 |
| ladd | 将栈顶两long型数值相加并将结果压入栈顶 |
| fadd | 将栈顶两float型数值相加并将结果压入栈顶 |
| dadd | 将栈顶两double型数值相加并将结果压入栈顶 |
| isub | 将栈顶两int型数值相减并将结果压入栈顶 |
| lsub | 将栈顶两long型数值相减并将结果压入栈顶 |
| fsub | 将栈顶两float型数值相减并将结果压入栈顶 |
| dsub | 将栈顶两double型数值相减并将结果压入栈顶 |
| imul | 将栈顶两int型数值相乘并将结果压入栈顶 |
| lmul | 将栈顶两long型数值相乘并将结果压入栈顶 |
| fmul | 将栈顶两float型数值相乘并将结果压入栈顶 |
| dmul | 将栈顶两double型数值相乘并将结果压入栈顶 |
| idiv | 将栈顶两int型数值相除并将结果压入栈顶 |
| ldiv | 将栈顶两long型数值相除并将结果压入栈顶 |
| fdiv | 将栈顶两float型数值相除并将结果压入栈顶 |
| ddiv | 将栈顶两double型数值相除并将结果压入栈顶 |
| irem | 将栈顶两int型数值作取模运算并将结果压入栈顶 |
| lrem | 将栈顶两long型数值作取模运算并将结果压入栈顶 |
| frem | 将栈顶两float型数值作取模运算并将结果压入栈顶 |
| drem | 将栈顶两double型数值作取模运算并将结果压入栈顶 |
| ineg | 将栈顶int型数值取负并将结果压入栈顶 |
| lneg | 将栈顶long型数值取负并将结果压入栈顶 |
| fneg | 将栈顶float型数值取负并将结果压入栈顶 |
| dneg | 将栈顶double型数值取负并将结果压入栈顶 |
| ishl | 将int型数值左移指定位数并将结果压入栈顶 |
| lshl | 将long型数值左移指定位数并将结果压入栈顶 |
| ishr | 将int型数值右(带符号)移指定位数并将结果压入栈顶 |
| lshr | 将long型数值右(带符号)移指定位数并将结果压入栈顶 |
| iushr | 将int型数值右(无符号)移指定位数并将结果压入栈顶 |
| lushr | 将long型数值右(无符号)移指定位数并将结果压入栈顶 |
| iand | 将栈顶两int型数值”按位与”并将结果压入栈顶 |
| land | 将栈顶两long型数值”按位与”并将结果压入栈顶 |
| ior | 将栈顶两int型数值”按位或”并将结果压入栈顶 |
| lor | 将栈顶两long型数值”按位或”并将结果压入栈顶 |
| ixor | 将栈顶两int型数值”按位异或”并将结果压入栈顶 |
| lxor | 将栈顶两long型数值”按位异或”并将结果压入栈顶 |
| iinc | 将指定int型变量增加指定值(如i++, i—, i+=2等) |
| i2l | 将栈顶int型数值强制转换为long型数值并将结果压入栈顶 |
| i2f | 将栈顶int型数值强制转换为float型数值并将结果压入栈顶 |
| i2d | 将栈顶int型数值强制转换为double型数值并将结果压入栈顶 |
| l2i | 将栈顶long型数值强制转换为int型数值并将结果压入栈顶 |
| l2f | 将栈顶long型数值强制转换为float型数值并将结果压入栈顶 |
| l2d | 将栈顶long型数值强制转换为double型数值并将结果压入栈顶 |
| f2i | 将栈顶float型数值强制转换为int型数值并将结果压入栈顶 |
| f2l | 将栈顶float型数值强制转换为long型数值并将结果压入栈顶 |
| f2d | 将栈顶float型数值强制转换为double型数值并将结果压入栈顶 |
| d2i | 将栈顶double型数值强制转换为int型数值并将结果压入栈顶 |
| d2l | 将栈顶double型数值强制转换为long型数值并将结果压入栈顶 |
| d2f | 将栈顶double型数值强制转换为float型数值并将结果压入栈顶 |
| i2b | 将栈顶int型数值强制转换为byte型数值并将结果压入栈顶 |
| i2c | 将栈顶int型数值强制转换为char型数值并将结果压入栈顶 |
| i2s | 将栈顶int型数值强制转换为short型数值并将结果压入栈顶 |
| lcmp | 比较栈顶两long型数值大小, 并将结果(1, 0或-1)压入栈顶 |
| fcmpl | 比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶 |
| fcmpg | 比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶 |
| dcmpl | 比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶 |
| dcmpg | 比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶 |
| ifeq | 当栈顶int型数值等于0时跳转 |
| ifne | 当栈顶int型数值不等于0时跳转 |
| iflt | 当栈顶int型数值小于0时跳转 |
| ifge | 当栈顶int型数值大于等于0时跳转 |
| ifgt | 当栈顶int型数值大于0时跳转 |
| ifle | 当栈顶int型数值小于等于0时跳转 |
| if_icmpeq | 比较栈顶两int型数值大小, 当结果等于0时跳转 |
| if_icmpne | 比较栈顶两int型数值大小, 当结果不等于0时跳转 |
| if_icmplt | 比较栈顶两int型数值大小, 当结果小于0时跳转 |
| if_icmpge | 比较栈顶两int型数值大小, 当结果大于等于0时跳转 |
| if_icmpgt | 比较栈顶两int型数值大小, 当结果大于0时跳转 |
| if_icmple | 比较栈顶两int型数值大小, 当结果小于等于0时跳转 |
| if_acmpeq | 比较栈顶两引用型数值, 当结果相等时跳转 |
| if_acmpne | 比较栈顶两引用型数值, 当结果不相等时跳转 |
| goto | 无条件跳转 |
| jsr | 跳转至指定的16位offset位置, 并将jsr的下一条指令地址压入栈顶 |
| ret | 返回至本地变量指定的index的指令位置(一般与jsr或jsr_w联合使用) |
| tableswitch | 用于switch条件跳转, case值连续(可变长度指令) |
| lookupswitch | 用于switch条件跳转, case值不连续(可变长度指令) |
| ireturn | 从当前方法返回int |
| lreturn | 从当前方法返回long |
| freturn | 从当前方法返回float |
| dreturn | 从当前方法返回double |
| areturn | 从当前方法返回对象引用 |
| return | 从当前方法返回void |
| getstatic | 获取指定类的静态域, 并将其压入栈顶 |
| putstatic | 为指定类的静态域赋值 |
| getfield | 获取指定类的实例域, 并将其压入栈顶 |
| putfield | 为指定类的实例域赋值 |
| invokevirtual | 调用实例方法 |
| invokespecial | 调用超类构建方法, 实例初始化方法, 私有方法 |
| invokestatic | 调用静态方法 |
| invokeinterface | 调用接口方法 |
| invokedynamic | 调用动态方法 |
| new | 创建一个对象, 并将其引用引用值压入栈顶 |
| newarray | 创建一个指定的原始类型(如int, float, char等)的数组, 并将其引用值压入栈顶 |
| anewarray | 创建一个引用型(如类, 接口, 数组)的数组, 并将其引用值压入栈顶 |
| arraylength | 获取数组的长度值并压入栈顶 |
| athrow | 将栈顶的异常抛出 |
| checkcast | 检验类型转换, 检验未通过将抛出 ClassCastException |
| instanceof | 检验对象是否是指定类的实际, 如果是将1压入栈顶, 否则将0压入栈顶 |
| monitorenter | 获得对象的锁, 用于同步方法或同步块 |
| monitorexit | 释放对象的锁, 用于同步方法或同步块 |
| wide | 扩展本地变量的宽度 |
| multianewarray | 创建指定类型和指定维度的多维数组(执行该指令时, 操作栈中必须包含各维度的长度值), 并将其引用压入栈顶 |
| ifnull | 为null时跳转 |
| ifnonnull | 不为null时跳转 |
| goto_w | 无条件跳转(宽索引) |
| jsr_w | 跳转至指定的32位offset位置, 并将jsr_w的下一条指令地址压入栈顶 |
计算模型
Java字节码的计算模型是面向堆栈结构计算机的。例如,一个x86处理器的汇编代码如下
mov eax, byte [ebp-4]
mov edx, byte [ebp-8]
add eax, edx
mov ecx, eax这段代码将两个数值相加,并存入另一个地址。相似的反汇编字节码如下
0 iload_1
1 iload_2
2 iadd
3 istore_3在这里,需要相加的两个操作数被放入堆栈,而相加操作就在栈中进行,其结果也被放入堆栈。存储指令之后把栈顶的数据放入一个变量地址。在每条指令前面的数字仅仅是表示这条指令到方法开始处的偏移值。这种堆栈结构也可以推广到面向对象模型上。例如,有一个”getName”方法如下
Method java.lang.String getName()
0 aload_0 // "this"对象被存入变量0
1 getfield #5 <Field java.lang.String name>
// 这个指令从栈顶取出一个对象,并从中搜索一个指定的域
// 并将相应的数据存入栈顶。
// 这个例子中,"name"域对应于该类中的第五个常量。
4 areturn // 返回栈顶的对象作为函数的返回值例子
考虑如下Java代码
outer:
for (int i = 2; i < 1000; i++) {
for (int j = 2; j < i; j++) {
if (i % j == 0)
continue outer;
}
System.out.println (i);
}假设上述代码位于一个函数中,Java编译器可能将代码翻译成下述的Java字节码。
0: iconst_2
1: istore_1
2: iload_1
3: sipush 1000
6: if_icmpge 44
9: iconst_2
10: istore_2
11: iload_2
12: iload_1
13: if_icmpge 31
16: iload_1
17: iload_2
18: irem
19: ifne 25
22: goto 38
25: iinc 2, 1
28: goto 11
31: getstatic #84; //Field java/lang/System.out:Ljava/io/PrintStream;
34: iload_1
35: invokevirtual #85; //Method java/io/PrintStream.println:(I)V
38: iinc 1, 1
41: goto 2
44: return基于Java字节码的语言
Groovy, 一种基于Java的脚本语言 Scala,一种类型安全的通用编程语言,支持面向对象编程和函数式编程 Clojure, 一种函数式的通用编程语言,提供优秀的并发性。是一种LISP方言
对动态语言的支持
Java虚拟机对动态类型语言提供了一定的支持。但绝大多数的Java虚拟机指令集是基于静态类型语言的。在静态类型机制下,方法调用中的类型分析都是在编译时执行的,而且缺乏一种机制在运行时确定一个类型已经确定相应的方法。
JSR292中,在Java虚拟机层次增加了一种支持动态类型的指令invokedynamic,以支持在动态类型检测中的方法调用。 达芬奇机器则是一种支持这种动态类型调用的虚拟机。 而所有支持JSE 7的Java虚拟机都应支持invokedynamic操作码。