内存分析工具jol-core
- 1、简单使用
- 2、核心API与常用类
- 3、输出结果(表格)字段详解
- 4、查看数组内存布局
- 4.1、一维数组
- 4.2、不同基础类型数组
- 4.3、二维数组
- 5、分析内存泄露
1、简单使用
引入依赖:
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.17</version></dependency>publicclassBase{publicstaticvoidmain(String[]args){Demod=newDemo();System.out.println(ClassLayout.parseInstance(d).toPrintable());}}classDemo{inta;longb;}base.Demoobject internals:OFFSZTYPEDESCRIPTIONVALUE08(object header:mark)0x0000000000000001(non-biasable;age:0)84(object header:class)0xf800c143124intDemo.a0168longDemo.b0Instancesize:24bytesSpacelosses:0bytes internal+0bytes external=0bytes total2、核心API与常用类
- ClassLayout:最核心的类,用于分析类或对象的内存布局
parseIntstance(obj):解析对象实例的布局(包含具体字段值)parseClass(clazz):解析类定义的静态布局(不依赖实例,也会包含继承链上的字段)toPrintable():输出可读的内存布局详情instanceSize():获取对象的浅大小(Shallow Size)
- GraphLayout:用于分析对象引用图和整体内存占用
parseInstance(obj):解析对象及其引用链上的所有对象totalSize():计算该对象及其引用的所有对象的总内存(常用于排查内存泄露)
- VM:用于获取当前JVM的运行细节
current().details():打印 JVM 信息,如是否 64 位、是否开启指针压缩(UseCompressedOops)、对象对齐字节数等。
3、输出结果(表格)字段详解
当你调用 ClassLayout.parseInstance(obj).toPrintable()时,输出的表格各列含义如下:
- OFF / OFFSET:内存偏移量(单位字节),表示该区域在对象起始地址后的第几个字节开始。
- SZ / SIZE:该区域占用的内存大小(单位字节)。
- TYPE:字段的 Java 类型(对象头行通常为空)。
- DESCRIPTION:描述信息,关键标识包括:
- (object header):对象头(含 Mark Word 和 Class Pointer/Klass Pointer)。具体字段名(如 int a):实例数据字段。
- (alignment/padding gap)/ (loss due to …):对齐填充(Padding),JVM 要求对象大小通常是 8 字节的倍数。
- VALUE:内存中的实际值。对象头会展示为十六进制字节和二进制解释(可用来观察 synchronized 的锁标志位变化)。
典型实例与解读:
publicclassBase{publicstaticvoidmain(String[]args){System.out.println(ClassLayout.parseInstance(newObject()).toPrintable());}}以 64 位 HotSpot JVM(默认开启指针压缩)为例,一个空 new Object()的输出通常如下:
java.lang.Objectobject internals:OFFSZTYPEDESCRIPTIONVALUE08(object header:mark)0x0000000000000001(non-biasable;age:0)84(object header:class)0xf80001e5124(object alignment gap)Instancesize:16bytesSpacelosses:0bytes internal+4bytes external=4bytes total- 对象头:共 12 字节。前 8 字节是 Mark Word(存哈希码、GC 分代年龄、锁状态等),后 4 字节是 Class Pointer(类型指针,因压缩占 4 字节)。
- 对齐填充:4 字节。因为 12 字节不是 8 的倍数,JVM 补全 4 字节使其达到 16 字节。
- Instance size:这个对象实际占用了 16 字节堆空间。
4、查看数组内存布局
4.1、一维数组
publicclassBase{publicstaticvoidmain(String[]args){int[]arr=newint[10];System.out.println(ClassLayout.parseInstance(arr).toPrintable());}}典型输出(64 位 HotSpot,开启压缩指针)
[Iobject internals:OFFSZTYPEDESCRIPTIONVALUE08(object header:mark)0x0000000000000001(non-biasable;age:0)84(object header:class)0xf800016d124(array length)101640int[I.<elements>N/AInstancesize:56bytesSpacelosses:0bytes internal+0bytes external=0bytes total1. 数组对象头(比普通对象多 4 字节)
| OFFSET | SIZE | DESCRIPTION | 说明 |
|---|---|---|---|
| 0 | 8 | (object header: mark) | Mark Word(锁、GC、hash) |
| 8 | 4 | (object header: class) | Klass Pointer(压缩) |
| 12 | 4 | [I.<length> | 数组长度 |
数组对象头 = 16 bytes(普通对象是 12 bytes,数组多了 length)
2. 数组数据区(真正存元素的地方)
1640int[I.<elements>- 数组起始偏移:16
- 元素个数:10
- 每个 int:4 bytes
- 数据区大小:10 × 4 = 40 bytes
3. 总大小计算
对象头:16bytes 数据区:40bytes 对齐:0bytes ────────────── 总计:56bytes4.2、不同基础类型数组
1. byte[]
publicclassBase{publicstaticvoidmain(String[]args){byte[]arr=newbyte[10];System.out.println(ClassLayout.parseInstance(arr).toPrintable());}}[Bobject internals:OFFSZTYPEDESCRIPTIONVALUE08(object header:mark)0x0000000000000001(non-biasable;age:0)84(object header:class)0xf80000f5124(array length)101610byte[B.<elements>N/A266(object alignment gap)Instancesize:32bytesSpacelosses:0bytes internal+6bytes external=6bytes total对象头:16bytes 数据区:10×1=10bytes 对齐:6bytes(补到8的倍数) 总计:32bytes2. long[]
publicclassBase{publicstaticvoidmain(String[]args){long[]arr=newlong[10];System.out.println(ClassLayout.parseInstance(arr).toPrintable());}}[Jobject internals:OFFSZTYPEDESCRIPTIONVALUE08(object header:mark)0x0000000000000001(non-biasable;age:0)84(object header:class)0xf80001a9124(array length)101680long[J.<elements>N/AInstancesize:96bytesSpacelosses:0bytes internal+0bytes external=0bytes total对象头:16 bytes 数据区:10 × 8 = 80 bytes 对齐:0 总计:96 bytes对照表:
| 数组类型 | 对象头 | 元素大小 | 10 个元素总大小 |
|---|---|---|---|
| byte[] | 16 | 1 | 32 |
| int[] | 16 | 4 | 56 |
| long[] | 16 | 8 | 96 |
| char[] | 16 | 2 | 40 |
| double[] | 16 | 8 | 96 |
4.3、二维数组
publicclassBase{publicstaticvoidmain(String[]args){int[][]arr=newint[3][4];System.out.println(ClassLayout.parseInstance(arr).toPrintable());}}[[Iobject internals:OFFSZTYPEDESCRIPTIONVALUE08(object header:mark)0x0000000000000001(non-biasable;age:0)84(object header:class)0xf8009dca124(array length)31612[I[[I.<elements>N/A284(object alignment gap)Instancesize:32bytesSpacelosses:0bytes internal+4bytes external=4bytes total- arr本身是 引用数组
- 每个元素是一个 int[]
- JOL 只显示 外层数组的布局
对象头:16bytes 引用 ×3=12bytes 对齐:4bytes 总计:32bytes内层数组要单独分析:
System.out.println(ClassLayout.parseInstance(arr[0]).toPrintable());查看数组的“总内存占用”(含引用)
publicclassBase{publicstaticvoidmain(String[]args){int[][]arr=newint[3][4];System.out.println(GraphLayout.parseInstance(arr).toPrintable());}}[I@58372a00d, [I@6f496d9fd, [I@723279cfd object externals: ADDRESS SIZE TYPE PATH VALUE 76acc93b8 32 [I <r1> [0, 0, 0, 0] 76acc93d8 32 [I <r2> [0, 0, 0, 0] 76acc93f8 32 [I <r3> [0, 0, 0, 0] Addresses are stable after 1 tries.5、分析内存泄露
JOL(Java Object Layout)不是传统意义上的内存泄漏检测工具(不像 MAT / VisualVM / jprofiler),但它非常适合在“对象膨胀、重复引用、结构不合理”导致的慢泄漏场景中做微观分析。
JOL 分析内存泄漏的核心思路
- GraphLayout.totalSize()
看某个对象“拖带”了多少内存 - 对象引用链
看是谁在“偷偷引用” - 集合类内部结构
ArrayList / HashMap 扩容导致容量远大于实际使用 - 重复对象
同一内容被多次创建(如 String、DTO)
关键API:
- GraphLayout layout = GraphLayout.parseInstance(rootObject);
- totalSize():对象 + 引用对象的总内存
- toPrintable():打印完整对象图
- dominators():查找“支配者”
示例:
publicclassBase{publicstaticvoidmain(String[]args){Map<String,String>map=newHashMap<>();for(inti=0;i<10000;i++){map.put("key"+i,"value"+i);}GraphLayoutlayout=GraphLayout.parseInstance(map);System.out.println(layout.toPrintable());System.out.println("Total size: "+layout.totalSize());}}