Java——内存分析工具jol-core
·
内存分析工具jol-core
1、简单使用
引入依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
public class Base {
public static void main(String[] args) {
Demo d = new Demo();
System.out.println(ClassLayout.parseInstance(d).toPrintable());
}
}
class Demo {
int a;
long b;
}
base.Demo object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c143
12 4 int Demo.a 0
16 8 long Demo.b 0
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
2、核心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 的锁标志位变化)。
典型实例与解读:
public class Base {
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
}
}
以 64 位 HotSpot JVM(默认开启指针压缩)为例,一个空 new Object()的输出通常如下:
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- 对象头:共 12 字节。前 8 字节是 Mark Word(存哈希码、GC 分代年龄、锁状态等),后 4 字节是 Class Pointer(类型指针,因压缩占 4 字节)。
- 对齐填充:4 字节。因为 12 字节不是 8 的倍数,JVM 补全 4 字节使其达到 16 字节。
- Instance size:这个对象实际占用了 16 字节堆空间。
4、查看数组内存布局
4.1、一维数组
public class Base {
public static void main(String[] args) {
int[] arr = new int[10];
System.out.println(ClassLayout.parseInstance(arr).toPrintable());
}
}
典型输出(64 位 HotSpot,开启压缩指针)
[I object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800016d
12 4 (array length) 10
16 40 int [I.<elements> N/A
Instance size: 56 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
1. 数组对象头(比普通对象多 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. 数组数据区(真正存元素的地方)
16 40 int [I.<elements>
- 数组起始偏移:16
- 元素个数:10
- 每个 int:4 bytes
- 数据区大小:10 × 4 = 40 bytes
3. 总大小计算
对象头:16 bytes
数据区:40 bytes
对齐:0 bytes
──────────────
总计:56 bytes
4.2、不同基础类型数组
1. byte[]
public class Base {
public static void main(String[] args) {
byte[] arr = new byte[10];
System.out.println(ClassLayout.parseInstance(arr).toPrintable());
}
}
[B object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80000f5
12 4 (array length) 10
16 10 byte [B.<elements> N/A
26 6 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total
对象头:16 bytes
数据区:10 × 1 = 10 bytes
对齐:6 bytes(补到 8 的倍数)
总计:32 bytes
2. long[]
public class Base {
public static void main(String[] args) {
long[] arr = new long[10];
System.out.println(ClassLayout.parseInstance(arr).toPrintable());
}
}
[J object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001a9
12 4 (array length) 10
16 80 long [J.<elements> N/A
Instance size: 96 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes 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、二维数组
public class Base {
public static void main(String[] args) {
int[][] arr = new int[3][4];
System.out.println(ClassLayout.parseInstance(arr).toPrintable());
}
}
[[I object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf8009dca
12 4 (array length) 3
16 12 [I [[I.<elements> N/A
28 4 (object alignment gap)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- arr本身是 引用数组
- 每个元素是一个 int[]
- JOL 只显示 外层数组的布局
对象头:16 bytes
引用 × 3 = 12 bytes
对齐:4 bytes
总计:32 bytes
内层数组要单独分析:
System.out.println(ClassLayout.parseInstance(arr[0]).toPrintable());
查看数组的“总内存占用”(含引用)
public class Base {
public static void main(String[] args) {
int[][] arr = new int[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():查找“支配者”
示例:
public class Base {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
map.put("key" + i, "value" + i);
}
GraphLayout layout = GraphLayout.parseInstance(map);
System.out.println(layout.toPrintable());
System.out.println("Total size: " + layout.totalSize());
}
}
更多推荐


所有评论(0)