JVM跳转指令的偏移量怎么会是32768呢?

How can the offset for a JVM jump instruction be 32768?

在编写 时,我注意到 javac 的行为和生成的 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 - too much code to post here!)

            y++;
            y++;
        }
        System.out.println(y);
    }

}

那么生成的字节码将包含以下if_icmpge指令:

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
       ...

根据跳转指令的文档,偏移量(在本例中为 32768)计算如下:

If the comparison succeeds, the unsigned branchbyte1 and branchbyte2 are used to construct a signed 16-bit offset, where the offset is calculated to be (branchbyte1 << 8) | branchbyte2.

所以偏移量被称为有符号 16 位值。但是,signed 16 位值可以容纳的最大值是 32767,而不是 32768。

生成的class文件似乎仍然有效,可以正常执行。

我查看了 bytecode checking in the OpenJDK,(在我看来)这似乎只是因为括号错位才有效:

int jump = (((signed char)(code[offset+1])) << 8) + code[offset+2];

它将第一个字节转换为signed char然后 它将应用移位,并添加第二个字节。我本来希望它是

int jump = (((signed char)(code[offset+1]) << 8)) + code[offset+2];

甚至可能

int jump = (signed char)((code[offset+1]) << 8) + code[offset+2]);

但我不熟悉类型提升和可能的编译器特定的转换有符号和无符号类型的警告,所以我不确定这个转换背后是否有更深层次的含义...

那么32768的跳转偏移是否符合规范? OpenJDK 中的跳转计算代码在这方面是否有意义?

if_icmpge的参数是偏移量,但javap将跳转目标显示为绝对位置。也就是说,javap 应该在 32768: 处显示 getstatic 而不是 32770:(即 2 + 32768)。

我写了一个简单的 scala 代码来生成代码以进一步挖掘。对于各种跳转指令,偏移量是有符号的,支持向后,向前跳转。

如果偏移量小于等于 0x7FFF,我看到 goto 指令,如果偏移量大于 0x7FFF,我看到 goto_w 指令。

因此,Java 中的方法被限制为 65535 字节,因为 LineNumberTable、LocalVariableTable、exception_table... 被限制为 65535 字节。 JVM 使用 goto/goto_w 指令根据需要跳转带符号的 16/32 偏移量。