跳转到内容

Java 编程/字节码

来自 Wikibooks,开放世界中的开放书籍

Java 字节码是 Java 源代码被编译成的语言,也是 Java 虚拟机可以理解的语言。与必须为每种不同类型的计算机专门编译的编译语言不同,Java 程序只需要转换一次为字节码,之后它可以在任何存在 Java 虚拟机的平台上运行。

字节码是 Java 程序的编译格式。一旦 Java 程序被转换为字节码,它就可以通过网络传输并由 Java 虚拟机 (JVM) 执行。字节码文件通常具有 .class 扩展名。对于 Java 程序员来说,通常不需要了解字节码,但它可能很有用。



其他语言

[编辑 | 编辑源代码]

有一些激动人心的新语言正在被创建,它们也编译成 Java 字节码,例如 Groovy

GNAT
GNU Ada 编译器,能够将 Ada 编译成 Java 风格的字节码。
ftp://cs.nyu.edu/pub/gnat
JPython
将 Python 编译成 Java 风格的字节码。
http://www.jpython.org/
Kawa
将 Scheme 编译成 Java 风格的字节码。
http://www.gnu.org/software/kawa/


考虑以下 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 代码翻译成以下字节码,假设上面的代码被放在一个方法中

  Code:
   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             # remainder
   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

例如,我们可以编写一个简单的 Foo.java 源文件

public class Foo {
  public static void main(final String[] args) {
    System.out.println("This is a simple example of decompilation using javap");
    a();
    b();
  }
	
  public static void a() {
    System.out.println("Now we are calling a function...");
  }

  public static void b() {
    System.out.println("...and now we are calling b");
  }
}

编译它,然后将 Foo.java 移动到另一个目录,或者如果你愿意,删除它。我们可以用 javap 和 Foo.class 做什么?

$javap Foo

产生以下结果

Compiled from "Foo.java"
public class Foo extends java.lang.Object {
    public Foo();
    public static void main(java.lang.String[]);
    public static void a();
    public static void b();
}

如你所见,javac 编译器不会从 .class 文件中剥离任何(公共)变量名。因此,函数的名称、它们的參数和返回值的类型都暴露出来了。(这是为了让其他类能够访问它们所必需的。)

让我们再做一些,试试

$javap -c Foo
Compiled from "Foo.java"
public class Foo extends java.lang.Object{
public Foo();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc             #3; //String This is a simple example of decompilation using javap
   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   invokestatic    #5; //Method a:()V
   11:  invokestatic    #6; //Method b:()V
   14:  return

public static void a();
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc             #7; //String Now we are calling a function...
   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return

public static void b();
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc             #8; //String ...and now we are calling b
   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return

}

Java 字节码

[编辑 | 编辑源代码]

有关更详细的描述,请参阅 Oracle 的 Java 虚拟机规范[1]

操作数栈的操作表示为 [before]→[after],其中 [before] 是执行指令之前栈的状态,[after] 是执行指令之后栈的状态。一个栈,其顶部元素为 'b',其下一层元素为 'a',表示为 'a,b'。

助记符 操作码
(以 十六进制 表示)
其他字节
[before]→[after]
描述
A
aaload 32 arrayref, index → value 从数组中加载引用到堆栈
aastore 53 arrayref, index, value → 将引用存储到数组中
aconst_null 01 → null 将一个 null 引用推送到堆栈
aload 19 index → objectref 从本地变量 #index 中加载引用到堆栈
aload_0 2a → objectref 从本地变量 0 中加载引用到堆栈
aload_1 2b → objectref 从本地变量 1 中加载引用到堆栈
aload_2 2c → objectref 从本地变量 2 中加载引用到堆栈
aload_3 2d → objectref 从本地变量 3 中加载引用到堆栈
anewarray bd indexbyte1, indexbyte2 count → arrayref 创建一个新的引用数组,长度为 count,组件类型由常量池中 index (indexbyte1 << 8 + indexbyte2) 标识的类引用指定
areturn b0 objectref → [empty] 从方法中返回一个引用
arraylength be arrayref → length 获取数组的长度
astore 3a index objectref → 将引用存储到本地变量 #index
astore_0 4b objectref → 将引用存储到本地变量 0 中
astore_1 4c objectref → 将引用存储到本地变量 1 中
astore_2 4d objectref → 将引用存储到本地变量 2 中
astore_3 4e objectref → 将引用存储到本地变量 3 中
athrow bf objectref → [empty], objectref 抛出错误或异常(注意,其余堆栈会被清空,只留下对 Throwable 的引用)
B
baload 33 arrayref, index → value 从数组中加载一个字节或布尔值
bastore 54 arrayref, index, value → 将一个字节或布尔值存储到数组中
bipush 10 byte → value 将一个 byte 推送到堆栈作为整数 value
C
caload 34 arrayref, index → value 从数组中加载一个字符
castore 55 arrayref, index, value → 将一个字符存储到数组中
checkcast c0 indexbyte1, indexbyte2 objectref → objectref 检查一个 objectref 是否是某种类型,该类型的类引用位于常量池中的 index (indexbyte1 << 8 + indexbyte2) 位置
D
d2f 90 value → result 将一个双精度浮点数转换为单精度浮点数
d2i 8e value → result 将一个双精度浮点数转换为整数
d2l 8f value → result 将一个双精度浮点数转换为长整型
dadd 63 value1, value2 → result 将两个双精度浮点数相加
daload 31 arrayref, index → value 从数组中加载一个双精度浮点数
dastore 52 arrayref, index, value → 将一个双精度浮点数存储到数组中
dcmpg 98 value1, value2 → result 比较两个双精度浮点数
dcmpl 97 value1, value2 → result 比较两个双精度浮点数
dconst_0 0e → 0.0 将常量 0.0 推送到堆栈
dconst_1 0f → 1.0 将常量 1.0 推送到堆栈
ddiv 6f value1, value2 → result 将两个双精度浮点数相除
dload 18 index → value 从本地变量 #index 中加载一个双精度浮点数 value
dload_0 26 → value 从本地变量 0 中加载一个双精度浮点数
dload_1 27 → value 从本地变量 1 中加载一个双精度浮点数
dload_2 28 → value 从本地变量 2 中加载一个双精度浮点数
dload_3 29 → value 从本地变量 3 中加载一个双精度浮点数
dmul 6b value1, value2 → result 将两个双精度浮点数相乘
dneg 77 value → result 对一个双精度浮点数取负
drem 73 value1, value2 → result 获取两个双精度浮点数相除的余数
dreturn af value → [empty] 从方法中返回一个双精度浮点数
dstore 39 index value → 将一个双精度浮点数 value 存储到本地变量 #index
dstore_0 47 value → 将一个双精度浮点数存储到本地变量 0 中
dstore_1 48 value → 将一个双精度浮点数存储到本地变量 1 中
dstore_2 49 value → 将一个双精度浮点数存储到本地变量 2 中
dstore_3 4a value → 将一个双精度浮点数存储到本地变量 3 中
dsub 67 value1, value2 → result 从另一个双精度浮点数中减去一个双精度浮点数
dup 59 value → value, value 复制堆栈顶部的值
dup_x1 5a value2, value1 → value1, value2, value1 将顶部值的副本插入到距离顶部两个值的堆栈中
dup_x2 5b value3, value2, value1 → value1, value3, value2, value1 将顶部值的副本插入到距离顶部两个(如果 value2 是双精度浮点数或长整型,它也会占用 value3 的位置)或三个(如果 value2 既不是双精度浮点数也不是长整型)值的堆栈中
dup2 5c {value2, value1} → {value2, value1}, {value2, value1} 复制堆栈顶部的两个字(两个值,如果 value1 既不是双精度浮点数也不是长整型;一个值,如果 value1 是双精度浮点数或长整型)
dup2_x1 5d value3, {value2, value1} → {value2, value1}, value3, {value2, value1} 复制两个字并将它们插入到第三个字的下方(见上文解释)
dup2_x2 5e {value4, value3}, {value2, value1} → {value2, value1}, {value4, value3}, {value2, value1} 复制两个字并将它们插入到第四个字的下方
F
f2d 8d value → result 将一个单精度浮点数转换为双精度浮点数
f2i 8b value → result 将一个单精度浮点数转换为整数
f2l 8c value → result 将一个单精度浮点数转换为长整型
fadd 62 value1, value2 → result 将两个单精度浮点数相加
faload 30 arrayref, index → value 从数组中加载一个单精度浮点数
fastore 51 arreyref, index, value → 将一个单精度浮点数存储到数组中
fcmpg 96 value1, value2 → result 比较两个浮点数
fcmpl 95 value1, value2 → result 比较两个浮点数
fconst_0 0b → 0.0f 0.0f压入堆栈
fconst_1 0c → 1.0f 1.0f压入堆栈
fconst_2 0d → 2.0f 2.0f压入堆栈
fdiv 6e value1, value2 → result 除两个浮点数
fload 17 index → value 从局部变量#index加载浮点数value
fload_0 22 → value 从局部变量0加载浮点数value
fload_1 23 → value 从局部变量1加载浮点数value
fload_2 24 → value 从局部变量2加载浮点数value
fload_3 25 → value 从局部变量3加载浮点数value
fmul 6a value1, value2 → result 乘两个浮点数
fneg 76 value → result 取浮点数的负值
frem 72 value1, value2 → result 获取两个浮点数相除的余数
freturn ae value → [empty] 从方法返回一个浮点数
fstore 38 index value → 将浮点数value存储到局部变量#index
fstore_0 43 value → 将浮点数value存储到局部变量0
fstore_1 44 value → 将浮点数value存储到局部变量1
fstore_2 45 value → 将浮点数value存储到局部变量2
fstore_3 46 value → 将浮点数value存储到局部变量3
fsub 66 value1, value2 → result 减去两个浮点数
G
getfield b4 index1, index2 objectref → value 获取对象objectref的字段value,其中字段由常量池index中的字段引用标识(index1 << 8 + index2
getstatic b2 index1, index2 → value 获取类的静态字段value,其中字段由常量池index中的字段引用标识(index1 << 8 + index2
goto a7 branchbyte1, branchbyte2 [无变化] 转到branchoffset处的另一个指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
goto_w c8 branchbyte1, branchbyte2, branchbyte3, branchbyte4 [无变化] 转到branchoffset处的另一个指令(从无符号字节branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4构造的带符号整型)
I
i2b 91 value → result 将int转换为字节
i2c 92 value → result 将int转换为字符
i2d 87 value → result 将int转换为双精度浮点数
i2f 86 value → result 将int转换为单精度浮点数
i2l 85 value → result 将int转换为长整型
i2s 93 value → result 将int转换为短整型
iadd 60 value1, value2 → result 将两个int加在一起
iaload 2e arrayref, index → value 从数组中加载一个int
iand 7e value1, value2 → result 对两个整数执行逻辑与运算
iastore 4f arrayref, index, value → 将一个int存储到数组中
iconst_m1 02 → -1 将int值-1加载到堆栈中
iconst_0 03 → 0 将int值0加载到堆栈中
iconst_1 04 → 1 将int值1加载到堆栈中
iconst_2 05 → 2 将int值2加载到堆栈中
iconst_3 06 → 3 将int值3加载到堆栈中
iconst_4 07 → 4 将int值4加载到堆栈中
iconst_5 08 → 5 将int值5加载到堆栈中
idiv 6c value1, value2 → result 除两个整数
if_acmpeq a5 branchbyte1, branchbyte2 value1, value2 → 如果引用相等,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_acmpne a6 branchbyte1, branchbyte2 value1, value2 → 如果引用不相等,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_icmpeq 9f branchbyte1, branchbyte2 value1, value2 → 如果int相等,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_icmpne a0 branchbyte1, branchbyte2 value1, value2 → 如果int不相等,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_icmplt a1 branchbyte1, branchbyte2 value1, value2 → 如果value1小于value2,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_icmpge a2 branchbyte1, branchbyte2 value1, value2 → 如果value1大于或等于value2,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_icmpgt a3 branchbyte1, branchbyte2 value1, value2 → 如果value1大于value2,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
if_icmple a4 branchbyte1, branchbyte2 value1, value2 → 如果value1小于或等于value2,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifeq 99 branchbyte1, branchbyte2 value → 如果value为0,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifne 9a branchbyte1, branchbyte2 value → 如果value不为0,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
iflt 9b branchbyte1, branchbyte2 value → 如果value小于0,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifge 9c branchbyte1, branchbyte2 value → 如果value大于或等于0,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifgt 9d branchbyte1, branchbyte2 value → 如果value大于0,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifle 9e branchbyte1, branchbyte2 value → 如果value小于或等于0,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifnonnull c7 branchbyte1, branchbyte2 value → 如果value不为空,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
ifnull c6 branchbyte1, branchbyte2 value → 如果value为空,则跳转到branchoffset处的指令(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
iinc 84 index, const [无变化] 将局部变量#index增加带符号字节const
iload 15 index → value 从变量#index加载一个intvalue
iload_0 1a → value 从变量0加载一个intvalue
iload_1 1b → value 从变量1加载一个intvalue
iload_2 1c → value 从变量2加载一个intvalue
iload_3 1d → value 从变量3加载一个intvalue
imul 68 value1, value2 → result 乘两个整数
ineg 74 value → result 取int的负值
instanceof c1 indexbyte1, indexbyte2 objectref → result 确定对象objectref是否为给定类型,由常量池(indexbyte1 << 8 + indexbyte2)中的类引用index标识
invokedynamic ba indexbyte1, indexbyte2 [arg1, arg2, ...] → result 调用动态方法并将结果放到堆栈上(可能是void);该方法由常量池(indexbyte1 << 8 | indexbyte2)中的方法引用index标识
invokeinterface b9 indexbyte1, indexbyte2, count, 0 objectref, [arg1, arg2, ...] → 在对象objectref上调用接口方法,其中接口方法由常量池(indexbyte1 << 8 + indexbyte2)中的方法引用index标识,count是从堆栈帧中弹出的参数数量,包括调用方法的对象,并且必须始终大于或等于1
invokespecial b7 indexbyte1, indexbyte2 objectref, [arg1, arg2, ...] → 调用对象objectref上的实例方法,需要特殊处理(实例初始化方法、私有方法或超类方法),其中方法由常量池(indexbyte1 << 8 + indexbyte2)中的方法引用index标识
invokestatic b8 indexbyte1, indexbyte2 [arg1, arg2, ...] → 调用静态方法,其中方法由常量池(indexbyte1 << 8 + indexbyte2)中的方法引用index标识
invokevirtual b6 indexbyte1, indexbyte2 objectref, [arg1, arg2, ...] → 调用对象objectref上的虚拟方法,其中方法由常量池(indexbyte1 << 8 + indexbyte2)中的方法引用index标识
ior 80 value1, value2 → result 逻辑int或
irem 70 value1, value2 → result 逻辑int余数
ireturn ac value → [empty] 从方法返回一个整数
ishl 78 value1, value2 → result int左移
ishr 7a value1, value2 → result int右移
istore 36 index value → 将intvalue存储到变量#index
istore_0 3b value → 将intvalue存储到变量0中
istore_1 3c value → 将intvalue存储到变量1中
istore_2 3d value → 将intvalue存储到变量2中
istore_3 3e value → 将intvalue存储到变量3中
isub 64 value1, value2 → result int减法
iushr 7c value1, value2 → result int右移
ixor 82 value1, value2 → result int异或
J
jsr a8 branchbyte1, branchbyte2 → address 跳转到branchoffset处的子程序并把返回地址放到堆栈上(从无符号字节branchbyte1 << 8 + branchbyte2构造的带符号短整型)
jsr_w c9 branchbyte1, branchbyte2, branchbyte3, branchbyte4 → address 跳转到branchoffset处的子程序并把返回地址放到堆栈上(从无符号字节branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4构造的带符号整型)
L
l2d 8a value → result 将长整型转换为双精度浮点数
l2f 89 value → result 将长整型转换为单精度浮点数
l2i 88 value → result 将长整型转换为整数
ladd 61 value1, value2 → result 将两个长整型加在一起
laload 2f arrayref, index → value 从数组中加载一个长整型
land 7f value1, value2 → result 两个长整型的按位与运算
lastore 50 arrayref, index, value → 将一个长整型存储到数组中
lcmp 94 value1, value2 → result 比较两个长整型值
lconst_0 09 → 0L 将长整型0压入堆栈
lconst_1 0a → 1L 将长整型1压入堆栈
ldc 12 index → value 将常量池(字符串、int、float 或类类型)中的常量#index压入堆栈
ldc_w 13 indexbyte1, indexbyte2 → value 将常量池(字符串、int、float 或类类型)中的常量#index压入堆栈(宽index构造为indexbyte1 << 8 + indexbyte2
ldc2_w 14 indexbyte1, indexbyte2 → value 将常量池(双精度浮点数或长整型)中的常量#index压入堆栈(宽index构造为indexbyte1 << 8 + indexbyte2
ldiv 6d value1, value2 → result 将两个长整型数相除
lload 16 index → value 从局部变量 #index 中加载一个长整型值
lload_0 1e → value 从局部变量 0 中加载一个长整型值
lload_1 1f → value 从局部变量 1 中加载一个长整型值
lload_2 20 → value 从局部变量 2 中加载一个长整型值
lload_3 21 → value 从局部变量 3 中加载一个长整型值
lmul 69 value1, value2 → result 将两个长整型数相乘
lneg 75 value → result 对一个长整型数取反
lookupswitch ab <0-3 字节填充>, defaultbyte1, defaultbyte2, defaultbyte3, defaultbyte4, npairs1, npairs2, npairs3, npairs4, 匹配偏移对... 键 → 使用键从表中查找目标地址,并从该地址处的指令开始继续执行
lor 81 value1, value2 → result 两个长整型数的按位或运算
lrem 71 value1, value2 → result 两个长整型数相除的余数
lreturn ad value → [empty] 返回一个长整型值
lshl 79 value1, value2 → result 将长整型数 value1 按位左移 value2
lshr 7b value1, value2 → result 将长整型数 value1 按位右移 value2
lstore 37 index value → 将长整型数 value 存储到局部变量 #index
lstore_0 3f value → 将长整型数 value 存储到局部变量 0 中
lstore_1 40 value → 将长整型数 value 存储到局部变量 1 中
lstore_2 41 value → 将长整型数 value 存储到局部变量 2 中
lstore_3 42 value → 将长整型数 value 存储到局部变量 3 中
lsub 65 value1, value2 → result 将两个长整型数相减
lushr 7d value1, value2 → result 将长整型数 value1 按位右移 value2 位,无符号
lxor 83 value1, value2 → result 两个长整型数的按位异或运算
M
monitorenter c2 objectref → 进入对象的监视器(“获取锁” - 同步化()部分的开始)
monitorexit c3 objectref → 退出对象的监视器(“释放锁” - 同步化()部分的结束)
multianewarray c5 indexbyte1, indexbyte2, 维度 count1, [count2,...] → arrayref 创建一个新的数组,该数组具有 维度 维度,元素类型由常量池中的类引用 indexindexbyte1 << 8 + indexbyte2)标识;每个维度的尺寸由 count1, [count2, 等] 标识
N
new bb indexbyte1, indexbyte2 → objectref 创建一个类型为常量池中的类引用 indexindexbyte1 << 8 + indexbyte2)标识的新对象
newarray bc atype count → arrayref 创建一个包含 count 个由 atype 标识的原始类型元素的新数组
nop 00 [无变化] 不执行任何操作
P
pop 57 value → 丢弃堆栈顶部的值
pop2 58 {value2, value1} → 丢弃堆栈顶部的两个值(如果它是双精度或长整型,则丢弃一个值)
putfield b5 indexbyte1, indexbyte2 objectref, value → value 设置为对象 objectref 中的字段,其中字段由常量池中的字段引用 indexindexbyte1 << 8 + indexbyte2)标识
putstatic b3 indexbyte1, indexbyte2 value → value 设置为类中的静态字段,其中字段由常量池中的字段引用 indexindexbyte1 << 8 + indexbyte2)标识
R
ret a9 index [无变化] 从局部变量 #index 中获取的地址处继续执行(与 jsr 的不对称性是故意的)
return b1 → [空] 从方法中返回 void
S
saload 35 arrayref, index → value 从数组中加载短整型
sastore 56 arrayref, index, value → 将短整型存储到数组中
sipush 11 byte1, byte2 → value 将一个带符号的整型数(byte1 << 8 + byte2)压入堆栈
swap 5f value2, value1 → value1, value2 交换堆栈顶部的两个字(注意 value1 和 value2 不能是双精度或长整型)
T
W
未用
impdep1 fe 为调试器中的实现相关操作保留;不应出现在任何类文件中
impdep2 ff 为调试器中的实现相关操作保留;不应出现在任何类文件中
(无名称) cb-fd 这些值目前未分配给操作码,并为将来使用保留
xxxunusedxxx ba 此操作码“出于历史原因”保留

参考文献

[编辑 | 编辑源代码]
  1. 甲骨文公司的 Java 虚拟机规范
[编辑 | 编辑源代码]


华夏公益教科书