Dar*_*usz 1 java memory stack jvm
64 位机器上的对象Long可能需要 24 个字节:12 个字节用于对象头,8 个字节用于长值本身,另外 4 个字节用于填充。我很容易用谷歌搜索到这个,但我在JVM 规范中找不到它。
我在任何地方都找不到什么是放在堆栈上的原始变量类型大小。它们有衬垫吗?规则是什么?例如,以下方法中变量的堆栈内存占用是多少:
class MemoryTest{
static void foo() {
int anInt=0;
long aLong=0L;
byte aByte=0;
short aShort=0;
// some code that uses the vars above
}
}
Run Code Online (Sandbox Code Playgroud)
我很容易用谷歌搜索到这个,但我在 JVM 规范中找不到它。
嗯,那是因为它是一个实现细节。规范中未提及的所有内容都为实现提供了执行自己操作的空间。在这种情况下,规范在2.7中特别指出了这一点:
Java 虚拟机不强制要求对象有任何特定的内部结构。
JVM 的数据类型在2.3中定义。
积分类型有:
byte,其值为 8 位有符号补码整数,默认值为 0
short,其值为 16 位有符号补码整数,默认值为 0
int,其值为 32 位有符号二进制补码整数,其默认值为零
long,其值为 64 位有符号二进制补码整数,其默认值为零
char,其值为 16 位无符号整数,表示基本多语言平面中的 Unicode 代码点,使用 UTF-16 编码,其默认值为空代码点 ('\u0000')浮点类型有:
float,其值与 32 位 IEEE 754 二进制 32 格式表示的值完全对应,并且其默认值为正零
double,其值与 64 位 IEEE 754 二进制 64 格式的值完全对应,其默认值为正零
它们只有您期望的大小,没有像包装类那样的标头。然而,JVM 指令集是有限的,并不是每种类型都有对应的指令。事实上,大部分指令都是针对int、、、。(有关更多信息,请参阅此表)因此最终您几乎总是会使用这些类型。longfloatdouble
请注意,基本类型的值不会直接“放入堆栈”。JVM堆栈存储帧,“用于存储数据和部分结果,以及执行动态链接、方法的返回值和分派异常”。
在框架上,有一个“操作数堆栈”,其中存储部分结果。例如,实现可能0首先将代码中的 s 压入操作数堆栈,然后将它们弹出到局部变量中。
规范对操作数堆栈上的内容的大小进行了以下规定:
操作数堆栈上的每个条目都可以保存任何 Java 虚拟机类型的值,包括 type
long或 type的值double。[...]
在任何时间点,操作数堆栈都具有关联的深度,其中 或 类型的值
long贡献double两个单位的深度,而任何其他类型的值贡献一个单位的深度。
根据规范,局部变量也存储在框架中的数组中。与您所问问题相关的部分是:
单个局部变量可以保存
boolean、byte、char、short、int、float、reference、 或类型的值returnAddress。一对局部变量可以保存类型为long或的值double。[...]
long一个type或type的值double占用两个连续的局部变量。这样的值只能使用较小的索引来寻址。例如,double存储在索引为n的局部变量数组中的类型值实际上占用了索引为n和n+1的局部变量;但是,无法加载索引 n+1 处的局部变量。可以将其存储到. 然而,这样做会使局部变量 n 的内容无效。Java虚拟机不要求n是偶数。直观地说,类型
long和的值double不需要在局部变量数组中进行 64 位对齐。实现者可以自由决定使用为该值保留的两个局部变量来表示这些值的适当方式。
至于您的代码,我们需要做出一些合理的假设。我们假设javap生成了以下类文件(从 中查看)。我用我的javacusing编译了这个javac -g MemoryTest.java。
Classfile /Users/mulangsu/Desktop/MemoryTest.class
Last modified 2022/09/24; size 264 bytes
SHA-256 checksum c1aa63404c5e590ef52116de586a91d75deb8727ab749cbd1e0d6fb4197357c2
Compiled from "MemoryTest.java"
class MemoryTest
minor version: 0
major version: 61
flags: (0x0020) ACC_SUPER
this_class: #7 // MemoryTest
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Class #8 // MemoryTest
#8 = Utf8 MemoryTest
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 foo
#12 = Utf8 SourceFile
#13 = Utf8 MemoryTest.java
{
MemoryTest();
descriptor: ()V
flags: (0x0000)
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
static void foo();
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=2, locals=5, args_size=0
0: iconst_0
1: istore_0
2: lconst_0
3: lstore_1
4: iconst_0
5: istore_3
6: iconst_0
7: istore 4
9: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 4
line 6: 6
line 8: 9
LocalVariableTable:
Start Length Slot Name Signature
2 8 0 anInt I
4 6 1 aLong J
6 4 3 aByte B
9 1 4 aShort S
}
SourceFile: "MemoryTest.java"
Run Code Online (Sandbox Code Playgroud)
有趣的是,所有使用的指令都是 int 和 long 指令。没有bipushor sipush,即使编译器可以使用它,但我想这会使类文件稍微大一些。
这就是我之前描述的“将 0 放入操作数堆栈,然后将其弹出到局部变量”行为。
如果我们还假设每个局部变量槽实现为 4 个字节,那么局部变量总共将占用 20 个字节。anInt、aByte、aShort各占1个槽位,aLong各占2个槽位,总共5个槽位。