"空对象占 16 字节"是背下来的结论,大多数人没有真的验证过它,也没验证过加一个字段之后会变成多少。自己写一个只有 premain 方法的 Java agent,用 Instrumentation.getObjectSize 拿 JVM 自己报告的真实大小,不靠猜。
上一篇:StringBuilder 扩容:真实反射测出容量是 16→34→70→142 这样长的
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) 拿到真实字节数,全程不需要任何第三方库。
依次测几个只有细微差别的类,对照它们真实测出来的大小。
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。
每一行都是真实用 java.lang.instrument.Instrumentation.getObjectSize() 测得的结果(本机 64 位 JVM,默认压缩指针)。