一个java对象最小占用内存空间
Java对象的内存布局:
对象头(Header)
实例数据(Instance Data)
对齐填充(Padding)
在网上搜到了一篇博客讲的非常好:http://yueyemaitian.iteye.com/blog/2033046
大家可以用这个代码边看边验证,注意的是,运行这个程序需要通过javaagent注入Instrumentation,具体可以看原博客。我今天主要是总结下手动计算Java对象占用字节数的基本规则,下面详细分析一下。
【1】对象头
计算公式:
Class 模板对象头 = Mark World(1字宽)+Class Metadata Address(1字宽)
+Array length(1字宽);
字宽就是字长,32位机上一个字长32位(4字节),64位机上一个字长64位(8字节)。
普通对象头(非数组)在32位系统上占用8bytes,64位系统上占用16bytes。数组对象头则分别为12字节和24字节。
空对象是只有对象头,没有实例数据,也无需填充对齐。因为所有的Java非基本类型的对象都需要默认继承Object对象
,因此不论什么样的Java对象,其大小都必须是大于8byte(16byte)
。
在32位机上包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,因为Java对象大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。
这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。在JDK5.0以后,因为加入了自动类型装换,因此,Java虚拟机会在存储方面进行相应的优化。
【2】实例数据
原生类型(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位系统上每个占用4bytes(一个字长)
, 在64位系统上每个占用8bytes(一个字长)
。
Integer a = new Integer();
【3】对齐填充
HotSpot的对齐方式为8字节对齐
:
(对象头 + 实例数据 + padding) % 8等于0,且0 <= padding < 8
【4】指针压缩
对象占用的内存大小收到VM参数UseCompressedOops
的影响。
① 对对象头的影响
开启压缩(-XX:+UseCompressedOops
)对象头大小为12bytes(64位机器,不开启压缩则为16字节,开启压缩少了4字节-半个字长
)。
static class A {
int a;//4字节
}
A对象占用内存情况:
-
关闭指针压缩: 16+4=20不是8的倍数,所以+padding/4=24
这里也可以看到无实例数据和对齐补充的Object实例对象在64位机上内存大小–16字节sizeOf(new Object())=16
。 -
开启指针压缩: 12+4=16已经是8的倍数了,不需要再padding。
② 对reference类型的影响
64位机器上reference类型占用8个字节,开启指针压缩后占用4个字节,少半个字长
。
static class B2 {
int b2a;//基本类型4字节
Integer b2b;//引用类型在64bit机上为8字节
}
B2对象占用内存情况:
-
关闭指针压缩:16+4+8=28不是8的倍数,所以+padding/4=32
-
开启指针压缩: 12+4+4=20不是8的倍数,所以+padding/4=24
【5】数组对象
64位机器上,数组对象的对象头占用24个字节,启用压缩之后占用16个字节,少了一个字长
。之所以比普通对象占用内存多是因为需要额外的空间存储数组的长度。
先考虑下new Integer[0]占用的内存大小,长度为0,即是对象头的大小:
- 未开启压缩:24bytes
- 开启压缩后:16bytes
接着计算new Integer[1],new Integer[2],new Integer[3]和new Integer[4]就很容易了:
- 未开启压缩:
- 开启压缩:
拿new Integer[3]来具体解释下:
未开启压缩:24(对象头)+8*3=48,不需要padding;
开启压缩:16(对象头)+3*4=28,+padding/4=32,其他依次类推。
【6】复合对象
计算复合对象占用内存的大小其实就是运用上面几条规则,只是麻烦点。
① 对象本身的大小
直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小。但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小。
static class B {
int a;
int b;
}
static class C {
int ba;
B[] as = new B[3];
C() {
for (int i = 0; i < as.length; i++) {
as[i] = new B();
}
}
}
C对象占用内存大小:
- 未开启压缩: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 = 152bytes
- 开启压缩:
(12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(数组对象padding)) + (12+8+4(B对象padding))*3= 128bytes
实例分析为了复现这个问题,准备了4个简单类:
class AAAAA {}
class BBBBB {
int a = 1;
}
class CCCCC {
long a = 1L;
}
class DDDDD {
String s = "hello";
}
结果
有了对象各部分的内存占用大小,可以很轻松的计算出ABCD各对象在64位系统,且开启UseCompressedOops
参数时的大小。
- A对象只包含一个对象头,大小占12字节,不是8的倍数,需要加上4字节进行填充,一共占16字节
- B对象包含一个对象头和int类型,12+4=16,正好是8的倍数,不需要填充。
- C对象包含一个对象头和long类型,12+8=20,不是8的倍数,使用4个字节进行填充,占24字节
- D对象包含一个对象头和引用类型,12+4=16,正好是8的倍数,不需要填充。
参考博文:
细探究,Java对象创建的奥秘
http://www.cnblogs.com/magialmoon/p/3757767.html
链接:https://www.jianshu.com/p/194b745884a5
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
2018-03-08 一致性哈希算法原理