分析JVM对象内存结构需结合jmap+MAT查看运行时堆快照、JOL验证静态布局、jmap-histo统计类级别分布、jstat监控代际流动;Shallow Heap仅含对象头与字段,不含引用对象本身。

要分析 JVM 中对象的内存结构,关键在于结合命令行工具与代码级探针,从运行时快照和静态布局两个维度入手。不依赖图形界面,也能清晰看到对象头、实例字段、对齐填充等真实占用。
用 jmap 生成堆快照再用 MAT 查看对象结构
这是最常用、最贴近生产环境的方式。jmap 能导出实时堆镜像(hprof 文件),MAT 则能逐层展开对象引用链和内存布局。
- 先用 jmap -dump:format=b,file=heap.hprof <pid> 抓取正在运行 Java 进程的堆快照
- 把 heap.hprof 拖进 Eclipse MAT(Memory Analyzer Tool),打开 “Dominator Tree” 或 “Histogram”
- 右键某个类 → “List objects” → “with outgoing references”,就能看到每个实例的字段值、引用对象、Shallow Heap(仅自身大小)和 Retained Heap(连带持有的全部内存)
- 特别注意 Shallow Heap:它只算对象头 + 实例字段(不含引用对象本身),比如一个空 Object 在 64 位 JVM(开启压缩指针)下是 16 字节(12 字节头 + 4 字节对齐)
用 JOL(Java Object Layout)在编码阶段验证结构
JOL 是 OpenJDK 提供的轻量级工具库,适合开发期快速验证字段顺序、大小、填充行为,无需启动 JVM 或 dump。
- Maven 引入:<groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version>
- 写个测试类,new 出实例后调用 ClassLayout.parseInstance(obj).toPrintable()
- 输出会明确标出:对象头(Mark Word + 类指针)、各字段偏移量与大小、末尾是否需要对齐填充(如 8 字节对齐)
- 例如 char[] 字段会显示为 8 字节引用(64 位指针),而数组内容本身在另一块连续内存中,不在该对象 Shallow Heap 内
用 jmap -histo 查看类级别对象统计
不需要生成大文件,快速掌握“哪些类实例最多、占内存最多”,是定位潜在问题的第一步。
- 执行 jmap -histo <pid>,输出按实例数降序排列的类名、实例数、总字节数
- 重点关注那些实例数异常高、或单个类总大小排前几的条目,比如 char[]、byte[]、HashMap$Node 等——它们常是大对象或集合膨胀的源头
- 加 -F 参数可强制对无响应进程执行(需 root 权限),适合 hang 住但未崩溃的场景
配合 jstat 观察对象生命周期与代际分布
jstat 不展示单个对象,但它能告诉你对象在堆里“住哪、待多久、怎么流动”,间接反映结构合理性。
- jstat -gc <pid> 显示 Eden、S0/S1、Old 各区使用量和 GC 次数,若 Old 区增长快、GC 频繁,说明大量对象过早晋升,可能因大对象直接分配到老年代(-XX:PretenureSizeThreshold 控制)
- jstat -gccapacity <pid> 查看各区当前容量,结合 -histo 数据,可判断是否某类对象集中堆积在某一代
- 新生代对象本应短命,若发现 ArrayList 或 String 实例长期存活,就要检查是否被缓存误持、或构造方式不合理(如反复 new 大数组)

















