Mindprod指出这不是一个容易回答的问题:
JVM 可以自由地以任何内部喜欢的方式存储数据,无论是大端还是小端,以及任意数量的填充或开销,尽管基元的行为必须就像它们具有官方大小一样。
例如,JVM 或本机编译器可能决定存储boolean[]
64 位长块,例如BitSet
。它不必告诉您,只要程序给出相同的答案即可。
- 它可能会在堆栈上分配一些临时对象。
- 它可能会优化一些完全不存在的变量或方法调用,用常量替换它们。
- 它可能会对方法或循环进行版本控制,即编译一个方法的两个版本,每个版本都针对特定情况进行优化,然后预先决定调用哪个版本。
当然,硬件和操作系统有多层缓存,芯片缓存、SRAM 缓存、DRAM 缓存、普通 RAM 工作集和磁盘上的后备存储。您的数据可能在每个缓存级别都有重复。所有这些复杂性意味着您只能非常粗略地预测 RAM 消耗。
测量方法
您可以使用Instrumentation.getObjectSize()获取对象消耗的存储空间的估计值。
可视化actual对象布局、占用空间和引用,您可以使用JOL(Java 对象布局)工具.
对象标头和对象引用
在现代 64 位 JDK 中,对象具有 12 字节标头,填充为 8 字节的倍数,因此最小对象大小为 16 字节。对于 32 位 JVM,开销为 8 字节,填充为 4 字节的倍数。(From 德米特里·斯皮哈尔斯基的回答, 杰恩的回答, and Java世界.)
通常,引用在 32 位平台上为 4 个字节,在 64 位平台上最多为-Xmx32G
;以及 32Gb 以上的 8 个字节(-Xmx32G
). (See 压缩对象引用.)
因此,64 位 JVM 通常需要多出 30-50% 的堆空间。(我应该使用 32 位还是 64 位 JVM?,2012,JDK 1.7)
装箱类型、数组和字符串
与原始类型相比,盒装包装器有开销(来自Java世界):
Integer
: 16 字节结果比我预期的要差一些,因为int
值只能容纳 4 个额外字节。使用Integer
与将值存储为原始类型相比,我花费了 300% 的内存开销
Long
:也是 16 字节:显然,堆上的实际对象大小取决于特定 CPU 类型的特定 JVM 实现所完成的低级内存对齐。它看起来像一个Long
是 8 个字节的对象开销,加上实际的 long 值的 8 个字节。相比之下,Integer
有一个未使用的 4 字节漏洞,很可能是因为我使用的 JVM 强制在 8 字节字边界上对齐对象。
其他容器也很昂贵:
-
多维数组: 它提供了另一个惊喜。
开发人员通常采用类似的结构int[dim1][dim2]
在数值和科学计算方面。
In an int[dim1][dim2]
数组实例,每个嵌套int[dim2]
数组是一个Object
在自己的权利。每个都会增加通常的 16 字节数组开销。当我不需要三角形或参差不齐的数组时,这代表纯粹的开销。当数组维度差异很大时,影响会更大。
例如,一个int[128][2]
实例占用 3,600 字节。与 1,040 字节相比int[256]
实例使用(具有相同的容量),3,600 字节代表 246% 的开销。在极端情况下byte[256][1]
,开销系数几乎是19!与 C/C++ 情况相比,相同的语法不会增加任何存储开销。
-
String
: a String
的内存增长跟踪其内部字符数组的增长。但是,那String
class 又增加了 24 个字节的开销。
对于一个非空的String
如果大小为 10 个字符或更少,则相对于有用负载(每个字符 2 个字节加上长度 4 个字节)而言,增加的开销成本范围为 100% 到 400%。
结盟
考虑一下这个示例对象:
class X { // 8 bytes for reference to the class definition
int a; // 4 bytes
byte b; // 1 byte
Integer c = new Integer(); // 4 bytes for a reference
}
一个天真的总和表明,一个实例X
将使用 17 个字节。但是,由于对齐(也称为填充),JVM 以 8 字节的倍数分配内存,因此它会分配 24 字节,而不是 17 字节。