为什么在 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 值?
它在运行时不存储为byte
或short
,只是在字节码中。
假设您要将值 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
存储为 byte
或 short
首先为什么 bipush
或 short
: bipush
有 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
这是一段代码
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 值?
它在运行时不存储为byte
或short
,只是在字节码中。
假设您要将值 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
存储为 byte
或 short
首先为什么 bipush
或 short
: bipush
有 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