空的 arraylist 占约 40 字节(64 位 jvm + 压缩指针),这是其浅层大小,不含内部 object[10] 数组的 64 字节;实际内存压力需看 retained size,推荐用 jol 或 heap dump 分析。

空的 ArrayList 占多少内存?直接说结论:约 40 字节(64 位 JVM + 压缩指针)
它不是“零开销”,也不是“随便用没关系”。一个刚 new ArrayList() 出来的实例,底层会分配一个长度为 10 的 Object[] 数组——哪怕一个元素都没加。
拆解来看:
-
ArrayList对象头:12 字节(Mark Word 8 + Klass Pointer 4) -
ArrayList实例字段:3 个引用(elementData、size、modCount),其中elementData是 4 字节(压缩指针),size和modCount各占 4 字节整型 → 共 12 字节 - 内部数组
Object[10]:对象头 16 字节 + 10 × 4 字节引用 = 56 字节 → 对齐到 8 字节倍数 → 实际占 64 字节 - 加总后对齐填充:12 + 12 + 64 = 88 → 向上对齐到 96 字节?不对。实际经
JOL(Java Object Layout)验证,在典型 OpenJDK 17+ 环境下,ArrayList实例浅层大小为 40 字节,其内部Object[10]单独算为 64 字节,二者不合并计——因为elementData是引用,getObjectSize()默认只算浅层,不算引用目标。
所以如果你用 Instrumentation.getObjectSize(new ArrayList()),结果通常是 40;而用 JOL 查看整个对象图,你会发现真正吃内存的是那个默默存在的 Object[10]。
为什么不能靠 getObjectSize() 准确评估集合真实内存压力
这个方法只返回“浅层大小”(Shallow Size),也就是对象自己占多少,不包括它引用的数组、节点、Entry 等。对集合来说,这几乎等于“只看了门牌号,没进屋看家具”。
常见误判场景:
- 你看到
HashMap浅层大小才 48 字节,以为很轻量 → 实际它默认初始化一个Node[16]数组(64 字节)+ 每个Node至少 32 字节(key/value/hash/next),插 1 万条就爆几百 MB -
new ArrayList()返回 40 字节,但你后续 add 600 万个Long,每个Long对象实占 24 字节 → 光装箱对象就 137 MB,再加上数组引用 24 MB,总开销远超 160 MB - 用
ArrayList<arraylist>></arraylist>嵌套三层?每层都带自己的数组和对象头,浅层大小完全失真
真正要摸清内存水位,得用 VisualVM 或 Eclipse MAT 抓 heap dump,看 “Retained Size” —— 它告诉你:删掉这个 ArrayList,JVM 能回收多少内存。
怎么避免空集合偷偷吃掉大量内存
空集合不是问题,但“批量创建却永不使用”的空集合是隐形负债。尤其在缓存、DTO、分页封装等场景中极易泛滥。
- 别写
return new ArrayList();当默认值,改用Collections.emptyList()—— 它返回的是共享的、不可变的单例,浅层大小 ≈ 16 字节(纯对象头) - 如果必须可变,且确定后续会 add 大量元素,直接指定初始容量:
new ArrayList(expectedSize),避免扩容复制和冗余空间 - Spring 中 Controller 返回
List,别让 MyBatis 或 JPA 自动 new 出一堆空ArrayList;可在 mapper 层用resultType="java.util.LinkedHashMap"或流式处理规避 - 注意 ORM 框架的懒加载代理集合(如 Hibernate 的
PersistentBag),它们即使为空,也可能持有一个未初始化但已预留结构的容器,内存行为更难预测
用 JOL 验证比凭经验靠谱得多
光背规则容易错,JVM 版本、是否开启压缩指针、甚至 GC 类型都会影响对象布局。唯一稳的方法是实测。
- 加依赖:
org.openjdk.jol:jol-core - 写一行代码:
System.out.println(VM.current().details());<br>System.out.println(ClassLayout.parseClass(ArrayList.class).toPrintable());<br>System.out.println(ClassLayout.parseInstance(new ArrayList<>()).toPrintable());
- 重点关注输出里的
OFFSET、SIZE和最后的Instance size,它才是真实占用字节数 - 对比
new ArrayList()和new ArrayList(1000),你会直观看到数组字段偏移和大小变化
对象内存从来不是“写了 new 就完事”,而是每次分配都在跟 JVM 的对齐策略、GC 压力、CPU 缓存行博弈。空集合看着小,堆里攒几百个,就能让 GC 多跑一轮 —— 这种细节,不测,真不知道。










