Published on

Java字节码和指令集

Authors

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.对于更高的变量,后缀将被去除,而这条指令将需要操作数。

指令集

助记词说明
nopNone
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_x1dup_x1指令的双倍版本
dup2_x2dup_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操作码。

参考连接