这些 Java 字节偏移量是如何计算的?
How are these Java byte offsets calculated?
我有以下 Java 代码:
public int sign(int a) {
if(a<0) return -1;
else if (a>0) return 1;
else return 0;
}
编译后生成以下字节码:
public int sign(int);
Code:
0: iload_1
1: ifge 6
4: iconst_m1
5: ireturn
6: iload_1
7: ifle 12
10: iconst_1
11: ireturn
12: iconst_0
13: ireturn
我想知道字节偏移量(第一列)是如何计算的,特别是,为什么 ifge
和 ifle
指令的字节数是 3 个字节,而其他指令的字节数是 3 个字节指令是单字节指令?
字节偏移量可以通过将其之前的每条指令的大小相加来轻松计算。指令大小记录在 JVM specs.
if<cond>
指令比其他指令占用更多 space,因为除了单字节操作码外,它们还有两个额外的字节,用于指定条件为真时跳转到的偏移量。
如果您想进一步试验,您可以尝试在代码中使用更大的常量(例如 20)。您会看到加载这些指令的指令也会使用额外的字节来存储常量值。为了提高效率,常用的小数都有一个字节编码(如iconst_1
)。
正如评论中已经指出的那样:ifge
和 ifle
指令有一个额外的偏移量。
Java Virtual Machine Instruction Set specification for ifge
and ifle
包含相关提示:
Format
if<cond>
branchbyte1
branchbyte2
这表明有两个附加字节与该指令相关联,即"branch bytes"。这些字节组成一个 short
值来确定 偏移量 - 即当条件满足时指令指针应该 "jump" 多远。
Edit:
评论让我感到好奇:offset
被定义为 signed 16 位值,将跳转限制在 +/- 32k 的范围内。这并未涵盖可能方法的全部范围,may contain up to 65535 bytes according to the code_length
in the class file。
所以我创建了一个测试 class,看看会发生什么。这个 class 看起来像这样:
class FarJump
{
public static void main(String args[])
{
call(0, 1);
}
public static void call(int x, int y)
{
if (x < y)
{
y++;
y++;
... (10921 times) ...
y++;
y++;
}
System.out.println(y);
}
}
y++
行中的每一行都将被翻译成一条iinc
指令,由3个字节组成。所以得到的字节码是
public static void call(int, int);
Code:
0: iload_0
1: iload_1
2: if_icmpge 32768
5: iinc 1, 1
8: iinc 1, 1
...(10921 times) ...
32762: iinc 1, 1
32765: iinc 1, 1
32768: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
32771: iload_1
32772: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
32775: return
可以看到还是用了一条if_icmpge
指令,偏移量为32768(编辑:是绝对偏移量。相对 偏移量为 32766。另见 )
在原来的代码中多加了一个y++
,编译后的代码突然变成了
public static void call(int, int);
Code:
0: iload_0
1: iload_1
2: if_icmplt 10
5: goto_w 32781
10: iinc 1, 1
13: iinc 1, 1
....
32770: iinc 1, 1
32773: iinc 1, 1
32776: goto_w 32781
32781: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
32784: iload_1
32785: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
32788: return
因此它将条件从 if_icmpge
反转为 if_icmplt
,并使用包含 four 的 goto_w
指令处理远跳转分支字节,因此可以覆盖(超过)一个完整的方法范围。
我有以下 Java 代码:
public int sign(int a) {
if(a<0) return -1;
else if (a>0) return 1;
else return 0;
}
编译后生成以下字节码:
public int sign(int);
Code:
0: iload_1
1: ifge 6
4: iconst_m1
5: ireturn
6: iload_1
7: ifle 12
10: iconst_1
11: ireturn
12: iconst_0
13: ireturn
我想知道字节偏移量(第一列)是如何计算的,特别是,为什么 ifge
和 ifle
指令的字节数是 3 个字节,而其他指令的字节数是 3 个字节指令是单字节指令?
字节偏移量可以通过将其之前的每条指令的大小相加来轻松计算。指令大小记录在 JVM specs.
if<cond>
指令比其他指令占用更多 space,因为除了单字节操作码外,它们还有两个额外的字节,用于指定条件为真时跳转到的偏移量。
如果您想进一步试验,您可以尝试在代码中使用更大的常量(例如 20)。您会看到加载这些指令的指令也会使用额外的字节来存储常量值。为了提高效率,常用的小数都有一个字节编码(如iconst_1
)。
正如评论中已经指出的那样:ifge
和 ifle
指令有一个额外的偏移量。
Java Virtual Machine Instruction Set specification for ifge
and ifle
包含相关提示:
Format
if<cond> branchbyte1 branchbyte2
这表明有两个附加字节与该指令相关联,即"branch bytes"。这些字节组成一个 short
值来确定 偏移量 - 即当条件满足时指令指针应该 "jump" 多远。
Edit:
评论让我感到好奇:offset
被定义为 signed 16 位值,将跳转限制在 +/- 32k 的范围内。这并未涵盖可能方法的全部范围,may contain up to 65535 bytes according to the code_length
in the class file。
所以我创建了一个测试 class,看看会发生什么。这个 class 看起来像这样:
class FarJump
{
public static void main(String args[])
{
call(0, 1);
}
public static void call(int x, int y)
{
if (x < y)
{
y++;
y++;
... (10921 times) ...
y++;
y++;
}
System.out.println(y);
}
}
y++
行中的每一行都将被翻译成一条iinc
指令,由3个字节组成。所以得到的字节码是
public static void call(int, int);
Code:
0: iload_0
1: iload_1
2: if_icmpge 32768
5: iinc 1, 1
8: iinc 1, 1
...(10921 times) ...
32762: iinc 1, 1
32765: iinc 1, 1
32768: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
32771: iload_1
32772: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
32775: return
可以看到还是用了一条if_icmpge
指令,偏移量为32768(编辑:是绝对偏移量。相对 偏移量为 32766。另见
在原来的代码中多加了一个y++
,编译后的代码突然变成了
public static void call(int, int);
Code:
0: iload_0
1: iload_1
2: if_icmplt 10
5: goto_w 32781
10: iinc 1, 1
13: iinc 1, 1
....
32770: iinc 1, 1
32773: iinc 1, 1
32776: goto_w 32781
32781: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
32784: iload_1
32785: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
32788: return
因此它将条件从 if_icmpge
反转为 if_icmplt
,并使用包含 four 的 goto_w
指令处理远跳转分支字节,因此可以覆盖(超过)一个完整的方法范围。