集合框架存的都是对象引用,而不是对象本身
在 Java 中,一个空 Object 对象的大小是 8 byte,这个大小只是保存堆中一个没有任何属性的对象的大小
Object ob = new Object();
这样在程序中完成了一个 Java 对象的生命,但是它所占的空间为:4 byte + 8 byte。4 byte 是上面部分所说的 Java 栈中保存引用的所需要的空间。而那 8 byte 则是 Java 堆中对象的信息。因为所有的 Java 非基本类型的对象都需要默认继承 Object 对象,因此不论什么样的 Java 对象,其大小都必须是大于 8 byte(上面是 32 位操作系统,64 位是 8 + 16)
Java 对象的内存布局
Java 对象的内存布局:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)
对象头
对象头在 32 位系统上占用 8 bytes,64 位系统上占用 16 bytes
实例数据
原生类型(primitive type)的内存占用如下:
Primitive Type | Memory Required(bytes) |
---|---|
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
reference 类型在 32 位系统上每个占用 4 bytes,在 64 位系统上每个占用 8 bytes
对齐填充
HotSpot 的对齐方式为 8 字节对齐:
(对象头 + 实例数据 + padding)% 8 等于 0 且 0 <= padding < 8
指针压缩
对象占用的内存大小收到 VM 参数 UseCompressedOops 的影响
对对象头的影响
开启(-XX:+UseCompressedOops)对象头大小为 12 bytes(64 位机器)
class A {
int a;
}
A 对象占用内存情况:
- 关闭指针压缩: 16 + 4 = 20 不是 8 的倍数,所以加 padding(4) = 24
- 开启指针压缩: 12 + 4 = 16 已经是 8 的倍数了,不需要再加 padding
对 reference 类型的影响
64 位机器上 reference 类型占用 8 个字节,开启指针压缩后占用 4 个字节
class B {
int b2a;
Integer b2b;
}
B 对象占用内存情况:
- 关闭指针压缩: 16 + 4 + 8 = 28 不是 8 的倍数,所以加 padding(4) = 32
- 开启指针压缩: 12 + 4 + 4 = 20 不是 8 的倍数,所以加 padding(4) = 24
数组对象
64 位机器上,数组对象的对象头占用 24 个字节,启用压缩之后占用 16 个字节。之所以比普通对象占用内存多是因为需要额外的空间存储数组的长度
先考虑下 new Integer[0] 占用的内存大小,长度为 0,即是对象头的大小:
- 未开启压缩:24 bytes
- 开启压缩后:16 bytes
new Integer[3] :
- 未开启压缩:24(对象头)+ 8 * 3 = 48,不需要 padding
- 开启压缩:16(对象头)+ 4 * 3 = 28,加 padding(4) = 32
自定义类的数组也是一样的:
class B3 {
int a;
Integer b;
}
new B3[3] 占用的内存大小:
- 未开启压缩:48
- 开启压缩后:32
复合对象
计算复合对象占用内存的大小其实就是运用上面几条规则,只是麻烦点
对象本身的大小
直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小; 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小
class B {
int a;
int b;
}
class C {
int ba;
B[] as = new B[3];
C() {
for (int i = 0; i < as.length; i++) {
as[i] = new B();
}
}
}
- 未开启压缩:16(对象头)+ 4(ba)+ 8(as 引用的大小)+ padding(4) = 32
- 开启压缩:12 + 4 + 4 + padding(4) = 24
当前对象占用的空间总大小
递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
递归计算复合对象占用的内存的时候需要注意的是:对齐填充是以每个对象为单位进行的
C 对象占用内存,主要是三部分构成:C 对象本身的大小 + 数组对象的大小 + B 对象的大小
未开启压缩:
(16 + 4 + 8 + 4(padding)) + (24 + 8 * 3) + (16 + 8) * 3 = 152 bytes开启压缩:
(12 + 4 + 4 + 4(padding)) + (16 + 4 * 3 + 4(数组对象 padding)) + (12 + 8 + 4(B对象padding))* 3 = 128 bytes