为什么在 JVM 中 Integer 存储为 byte 和 short?

Why in JVM Integer is stored as byte and short?

这是一段代码

public class Classifier {
    public static void main(String[] args) 
    {
        Integer x = -127;//this uses bipush
        Integer y = 127;//this use bipush
        Integer z= -129;//this use sipush
        Integer p=32767;//maximum range of short still sipush
        Integer a = 128; // use sipush
        Integer b = 129786;// invokes virtual method to get Integer class

    }

}

这是这个

的部分字节码
      stack=1, locals=7, args_size=1
         0: bipush        -127
         2: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
         5: astore_1
         6: bipush        127
         8: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        11: astore_2
        12: sipush        -129
        15: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        18: astore_3
        19: sipush        32767
        22: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        25: astore        4
        27: sipush        128
        30: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        33: astore        5
        35: ldc           #22                 // int 129786
        37: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;
        40: astore        6
        42: return

正如我看到的 -128 to 127 之间的整数范围,它使用 bipush 将一个字节作为整数值压入堆栈。 在范围 -32768 to 32767 中,它使用 short 作为包装器 class 作为 sipush。接下来它使用整数。什么 JVM 使用 byte 和 short 来存储 Integer 值?

它在运行时不存储为byteshort,只是在字节码中。 假设您要将值 120 存储到 Integer 中。您编写了编译器,因此您解析了源代码,并且您知道常量值 120 可以放入一个有符号字节中。因为您不想在字节码中浪费 space 来将值 120 存储为 32 位(4 字节)值,如果它可以存储为 8 位(1 字节),您将创建特殊指令,这将只能从方法字节码加载一个 byte 并将其作为 32 位 integer 存储在堆栈中。这意味着,在运行时,您确实拥有 integer 数据类型。

与在任何地方使用 ldc 相比,生成的代码更小更快,后者需要与 jvm 进行更多交互,因为需要使用运行时常量池进行操作。

bipush有2个字节,一个字节的操作码,第二个字节的立即constat值。因为你只有一个字节的值,它可以用于 -128 到 127 之间的值。

sipush有3个字节,一个字节操作码,第二个和第三个字节立即数 恒定值。

bipush格式:

bipush
byte

sipush格式:

sipush
byte1
byte2

据我所知。

从剩余的字节码指令中可以看出,它不会将 int 存储为 byteshort 首先为什么 bipushshortbipush 有 2 个字节,一个用于操作码,第二个用于值。即范围在 -128 tp 127 之间(即 2 次方 8) 它节省了 space 和执行时间。正如您从 remianing 代码中看到的那样,编译器确实将该变量的引用创建为整数类型

2: invokestatic  #16                 // Method java/lang/Integer.valueO
f:(I)Ljava/lang/Integer;

然后 astore_1 store what's on top of the stack i.e a reference here into local variable 1 sipush 的情况类似,您可以在其中存储 (-32768 to 32767) 范围内的值,因为它是 3 字节指令集,一个字节用于操作码, 剩下两个字节作为值(即可以容纳 2 的 16 次方)

现在为什么不 lDC JVM 有一个 per-type 常量池。字节码需要数据,但大多数时候 该数据太大,无法直接存储在字节码中。 所以它存储在常量池中,字节码包含对常量池的引用。 ldc 的作用是将常量池(String、int 或 float)中的常量 #index 压入堆栈 这会消耗额外的时间和周期 这是ldc操作和bipush操作

之间的粗略比较

JVM Bytecode ref 这里写着

Where possible, its more efficient to use one of bipush, sipush, or one of the const instructions instead of ldc.

一个原因可能是其他答案中提到的有关字节码的优点。

然而,也可以从语言开始对此进行推理。特别是,当(常量)值实际上可以用目标类型表示时,您不想插入强制转换。

因此,观察到的行为的一个原因是:编译器使用了可以表示给定常量值的最小可能类型

对于 int(或 Integer)的赋值是没有必要的——但当字节码将“较小”类型分配给“较大”类型时,它不会造成任何伤害类型。相反,对于较小的类型, 有必要使用较小的类型,因此使用“最小类型的字节码”是默认行为。


这在 Java 语言规范的 Section 5.2., Assignment Contexts 中也隐式称为“常量表达式的编译时缩小”:

A narrowing primitive conversion may be used if the type of the variable is byte, short, or char, and the value of the constant expression is representable in the type of the variable.

...

The compile-time narrowing of constant expressions means that code such as:

byte theAnswer = 42;

is allowed. Without the narrowing, the fact that the integer literal 42 has type int would mean that a cast to byte would be required:

byte theAnswer = (byte)42;  // cast is permitted but not required