jol可精确查看java对象内存布局,如jdk8中new string("a")占32字节,jdk11因字段优化减至24字节;数组大小总被8字节对齐,如new int[0]实际占16字节;classlayout看静态结构,graphlayout算递归总大小。

怎么用JOL看一个对象实际占多少字节
Java对象在内存里不是“想占多大就多大”,受对齐、头信息、字段重排影响,new Object() 都不止 0 字节。JOL(Java Object Layout)是唯一能让你直接看到 JVM 实际分配布局的工具。
最简实操路径:
- 加 Maven 依赖:
org.openjdk.jol:jol-core:0.17 - 写一行代码:
System.out.println(VM.current().details());确认当前 JVM 是否支持(比如某些容器环境禁用Unsafe就会失败) - 查对象:用
ClassLayout.parseClass(Foo.class).toPrintable()或GraphLayout.parseInstance(obj).toPrintable()
注意:parseClass 看的是类定义的静态布局(含继承链),parseInstance 看的是运行时实例(含实际值、数组长度等)。别混用。
为什么String在JDK 8和JDK 11里大小不一样
因为字段变了——JDK 9+ 把 char[] value 换成了 byte[] value + byte coder,省空间但改了布局逻辑。
立即学习“Java免费学习笔记(深入)”;
实测对比(64位JVM + CompressedOops 开启):
- JDK 8 中
new String("a")占 32 字节(对象头 12 +char[1]20) - JDK 11 中同样字符串只占 24 字节(对象头 12 +
byte[1]12)
关键点:coder 字段占 1 字节,但因对齐规则,整个对象仍按 8 字节边界填充。别只数字段字节数,要看 toPrintable() 输出里的 offset 和 gap 行。
数组对象的size为什么总被8整除
JVM 要求对象起始地址必须是 8 字节对齐,数组还得额外存长度字段(4 字节),所以数组对象头固定 12 字节(Mark Word 8 + Class Pointer 4),之后才是元素区。
常见误解:以为 new int[0] 是 12 字节。实际是 16 字节——因为 12 不满足 8 对齐,必须补到 16。
-
new byte[0]→ 16 字节 -
new long[1]→ 12(头)+ 8(元素) = 20 → 补到 24 字节 -
new Object[1]→ 12(头)+ 4(引用,CompressedOops) = 16 → 刚好对齐,不补
如果关掉 CompressedOops(用 -XX:-UseCompressedOops),所有引用变 8 字节,数组大小会跳涨——这点在堆内存压测时容易翻车。
为什么JOL显示的size和Runtime.totalMemory()差很远
totalMemory() 返回的是 JVM 已从 OS 申请、但尚未全部分配给对象的堆内存总量,而 JOL 给的是单个对象**精确到字节的内存占用**,两者不在同一抽象层。
真正要注意的是:GraphLayout.parseInstance(x).totalSize() 包含 x 引用的所有可达对象(递归深拷贝式计算),而 ClassLayout 只算本体。
- 误用
GraphLayout查一个持有HashMap的对象,可能得到几 MB —— 它把整个 map 的桶、节点、key/value 全算进去了 - 想看“对象本身”开销,只用
ClassLayout.parseClass(X.class) - 想确认 GC 前后某对象是否泄漏,用
GraphLayout+System.gc()后比对,但得确保没其他强引用
对象布局不是静态快照,它依赖 JVM 版本、参数(尤其是 UseCompressedOops、ObjectAlignmentInBytes)、甚至是否开启分层编译。同一段代码,在不同机器上跑出不同 size,不奇怪。










