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与常用类

  1. ClassLayout:最核心的类,用于分析类或对象的内存布局
    • parseIntstance(obj):解析对象实例的布局(包含具体字段值)
    • parseClass(clazz):解析类定义的静态布局(不依赖实例,也会包含继承链上的字段)
    • toPrintable():输出可读的内存布局详情
    • instanceSize():获取对象的浅大小(Shallow Size)
  2. GraphLayout:用于分析对象引用图和整体内存占用
    • parseInstance(obj):解析对象及其引用链上的所有对象
    • totalSize():计算该对象及其引用的所有对象的总内存(常用于排查内存泄露)
  3. 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 分析内存泄漏的核心思路

  1. GraphLayout.totalSize()
    看某个对象“拖带”了多少内存
  2. 对象引用链
    看是谁在“偷偷引用”
  3. 集合类内部结构
    ArrayList / HashMap 扩容导致容量远大于实际使用
  4. 重复对象
    同一内容被多次创建(如 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());
    }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐