bigywater.tech
Java 内部原理 · 12

空对象到底占几个字节:真实用 Instrumentation 测,不是背下来的 16

"空对象占 16 字节"是背下来的结论,大多数人没有真的验证过它,也没验证过加一个字段之后会变成多少。自己写一个只有 premain 方法的 Java agent,用 Instrumentation.getObjectSize 拿 JVM 自己报告的真实大小,不靠猜。

16 字节
真实测出:空对象、只有一个 byte 字段、只有一个 int 字段,大小完全一样,都是 16
+1 个 long → 24
真实测出:只要加一个 long 字段就跳到 24 字节,不管字段声明顺序是什么,结果都一样

上一篇:StringBuilder 扩容:真实反射测出容量是 16→34→70→142 这样长的

思路:自己写一个 agent,问 JVM 要真实答案

Java 没有 sizeof,但 java.lang.instrument 包里的 Instrumentation 接口有 getObjectSize,能拿到 JVM 自己报告的对象浅层大小。

要用这个方法,得先有一个 Instrumentation 实例——最简单的办法是自己写一个 Java agent,在 premain 方法里把它存下来:

Agent.java public class Agent { static Instrumentation inst; public static void premain(String args, Instrumentation instrumentation) { inst = instrumentation; } public static long sizeOf(Object o) { return inst.getObjectSize(o); } }

打成 jar(MANIFEST.MF 里写 Premain-Class: Agent),运行目标程序时加 -javaagent:agent.jar,就能在代码里直接调用 Agent.sizeOf(obj) 拿到真实字节数,全程不需要任何第三方库。

真实验证:从空对象到加字段,大小怎么变

依次测几个只有细微差别的类,对照它们真实测出来的大小。

真实实测 · Instrumentation.getObjectSize
Empty        = 16 bytes
OneByte      = 16 bytes
TwoByte      = 16 bytes
OneInt       = 16 bytes
OneLong      = 24 bytes
IntThenByte  = 24 bytes
ByteThenInt  = 24 bytes
OneRef       = 16 bytes
Object[]{}   = 16 bytes
int[]{}      = 16 bytes
int[10]      = 56 bytes
Integer(100) = 16 bytes
new ArrayList<>() shallow = 24 bytes

这组真实数据能对上一套完整的解释:64 位 JVM 默认开启指针压缩(Compressed Oops)时,对象头是 12 字节(8 字节标记字 + 4 字节压缩后的类型指针),再加上 JVM 强制的 8 字节对齐——所有对象大小必须是 8 的倍数。空对象、一个 byte 字段、一个 int 字段,字段部分都不超过 4 字节,12 + 4 = 16 正好落在 8 的倍数上,不需要额外填充,所以真实测出来都是 16。一旦加一个 long 字段(8 字节),情况变了:long 类型要求 8 字节对齐,对象头 12 字节后面得先补 4 字节让 long 字段从第 16 字节开始,12+4+8=24,真实测出来正好是 24。

为什么 IntThenByte 和 ByteThenInt 大小一样?这两个类声明字段的顺序刚好相反(一个是先 int 后 byte,另一个反过来),但真实测出来的大小完全相同,都是 24。原因是 JVM 不按 Java 源码里字段的声明顺序来排布内存,而是会在类加载时自己重新排列字段顺序(通常按大小从大到小排,把小字段挪到能省填充的位置),目的是尽量减少对齐产生的空洞。所以"字段声明顺序会不会影响内存占用"这个问题,真实答案是:不会,JVM 已经帮你优化过了。

分步演示:一个 Java 对象在堆上真实占多少字节

每一行都是真实用 java.lang.instrument.Instrumentation.getObjectSize() 测得的结果(本机 64 位 JVM,默认压缩指针)。

用真实的Instrumentation.getObjectSize()测得的对象大小,受8字节对齐规则影响很大;而且它测的是浅层大小,不包含通过引用指向的其他对象
这一步新验证的对象

    参考与说明

    • 本文所有字节数均在本机真实运行 Java 17、开启默认的指针压缩(Compressed Oops)条件下用自写 agent 测得;关闭指针压缩(-XX:-UseCompressedOops)或堆超过 32GB 时,这些数字会不一样,原理不变。
    • 这套 getObjectSize 只测浅层大小(对象自身,不含它引用的其它对象),后面装箱内存那篇会用同一个 agent 做批量真实统计。
    • 没有涉及:开启 -XX:+UseCompressedClassPointers 之外其它 JVM 内存布局参数的组合效果、数组元素对齐的更多细节——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号