Java字节码和指令集

2023-06-30#coding#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操作码。

参考连接