1. 概述

本文深入探讨 JVM 如何在堆内存中布局对象和数组。

我们先从理论入手,再结合 HotSpot JVM 的实际内存布局进行分析。需要明确的是,运行时数据区的内存布局并未在 JVM 规范中强制定义,而是由具体实现自行决定(JVM Spec 引用)。因此,不同 JVM 实现可能采用不同的对象布局策略。

本文聚焦于 HotSpot JVM 的实现机制,后续提到的 JVM 均指 HotSpot。

⚠️ 注意:虽然文中会使用“JVM”简称,但所有分析均基于 HotSpot 实现。

2. 普通对象指针(OOPs)

HotSpot 使用一种称为 Ordinary Object Pointers(OOPs) 的数据结构来表示对象指针。所有对象和数组的指针都基于 oopDesc 结构体构建。

每个 oopDesc 包含两个核心部分:

  • Mark Word:存储对象元信息,如哈希码、锁状态、GC 标记等
  • Klass Word:指向类元数据(Klass),包含类名、修饰符、父类等信息

Mark Word 详解

Mark Word 是对象头的关键部分,用于存储:

  • 身份哈希码(Identity Hash Code)
  • 锁信息(偏向锁、轻量级锁、重量级锁)
  • 垃圾回收相关元数据(如分代年龄)

⚠️ 其大小在 32 位 JVM 中为 4 字节,64 位中为 8 字节。
⚠️ Java 15 起已弃用偏向锁(JEP 374),本文仅讨论普通对象。

Klass Word 详解

Klass Word 指向 Klass 结构,封装了语言层面的类信息。在 64 位 JVM 中,若启用压缩指针(Compressed Oops),该字段为 4 字节;否则为 8 字节。

对象头总览

类型 组成 最小大小(64位)
普通对象 Mark Word (8B) + Klass Word (4B) + 填充 16 字节
数组对象 普通对象头 + 4 字节长度字段 16 字节

✅ 所有对象大小都会对齐到 8 字节倍数(默认),因此即使字段总和不足也会填充。

3. 使用 JOL 分析内存布局

要直观查看对象内存布局,推荐使用 Java Object Layout(JOL) 工具。添加依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.10</version>
</dependency>

初始化查看 VM 配置:

System.out.println(VM.current().details());

输出示例:

# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

含义:

  • 引用:4 字节(压缩指针开启)
  • boolean/byte:1 字节
  • short/char:2 字节
  • int/float:4 字节
  • long/double:8 字节

❌ 若关闭压缩指针(-XX:-UseCompressedOops),引用变为 8 字节。

4. 内存布局实战案例

4.1 基础对象布局

public class SimpleInt {
    private int state;
}

分析类布局:

System.out.println(ClassLayout.parseClass(SimpleInt.class).toPrintable());

输出:

SimpleInt object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4    int SimpleInt.state                           N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

✅ 12 字节对象头(8B mark + 4B klass) + 4B int = 16B,无需额外填充。

4.2 身份哈希码存储

未重写 hashCode() 时,JVM 使用身份哈希码,并懒加载存储在 Mark Word 中。

SimpleInt instance = new SimpleInt();
System.out.println(ClassLayout.parseInstance(instance).toPrintable());

初始状态(未计算哈希码):

 OFFSET  SIZE   TYPE DESCRIPTION               VALUE
      0     4        (object header)           01 00 00 00
      4     4        (object header)           00 00 00 00
      8     4        (object header)           9b 1b 01 f8
     12     4    int SimpleInt.state           0

调用 System.identityHashCode(instance) 后:

 OFFSET  SIZE   TYPE DESCRIPTION               VALUE
      0     4        (object header)           01 25 b2 74
      4     4        (object header)           65 00 00 00
      8     4        (object header)           9b 1b 01 f8
     12     4    int SimpleInt.state           0

✅ 哈希码 1702146597 以小端序存储为 25 b2 74 65 → 反序 65 74 b2 25 即为原值。

4.3 内存对齐机制

JVM 默认将对象大小对齐到 8 字节倍数。

public class SimpleLong {
    private long state;
}

布局分析:

SimpleLong object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12     4        (alignment/padding gap)                  
     16     8   long SimpleLong.state                          N/A
Instance size: 24 bytes

✅ 头部 12B + long 8B = 20B → 填充 4B 达到 24B(8 的倍数)。

可通过 -XX:ObjectAlignmentInBytes=16 修改对齐单位,此时总大小变为 32B。

4.4 字段重排序优化

JVM 会自动重排字段顺序以减少填充浪费。

public class FieldsArrangement {
    private boolean first;
    private char second;
    private double third;
    private int fourth;
    private boolean fifth;
}

实际布局:

OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     4       int FieldsArrangement.fourth                  N/A
     16     8    double FieldsArrangement.third                   N/A
     24     2      char FieldsArrangement.second                  N/A
     26     1   boolean FieldsArrangement.first                   N/A
     27     1   boolean FieldsArrangement.fifth                   N/A
     28     4           (loss due to the next object alignment)

✅ JVM 按 int → double → char → boolean 降序排列,最大限度减少内部碎片。

4.5 锁信息存储

锁状态也存储在 Mark Word 中。

public class Lock {}

无锁状态:

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 
      4     4        (object header)                           00 00 00 00
      8     4        (object header)                           85 23 02 f8
     12     4        (loss due to the next object alignment)

加锁后(synchronized):

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           f0 78 12 03
      4     4        (object header)                           00 70 00 00
      8     4        (object header)                           85 23 02 f8

✅ Mark Word 位模式变化,记录了锁信息。

4.6 分代年龄(GC 年龄)

对象在 Survivor 区经历一次 Minor GC,年龄 +1,该信息也存在 Mark Word。

模拟代码:

volatile Object consumer;
Object instance = new Object();
long lastAddr = VM.current().addressOf(instance);

for (int i = 0; i < 10_000; i++) {
    long currentAddr = VM.current().addressOf(instance);
    if (currentAddr != lastAddr) {
        System.out.println(ClassLayout.parseInstance(instance).toPrintable());
    }

    for (int j = 0; j < 10_000; j++) {
        consumer = new Object(); // 制造垃圾
    }
    lastAddr = currentAddr;
}

Mark Word 变化趋势:

09 00 00 00 → age=1
11 00 00 00 → age=2
19 00 00 00 → age=3
21 00 00 00 → age=4
...

✅ 高 4 位记录 GC 年龄,每次晋升年龄递增。

4.7 避免伪共享:@Contended

@jdk.internal.vm.annotation.Contended 注解用于避免伪共享(False Sharing),通过填充使字段独占缓存行。

public class Isolated {
    @Contended
    private int v1;
    @Contended
    private long v2;
}

布局结果:

Isolated object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0    12        (object header)                           N/A
     12   128        (alignment/padding gap)                  
    140     4    int Isolated.i                                N/A
    144   128        (alignment/padding gap)                  
    272     8   long Isolated.l                                N/A
Instance size: 280 bytes

✅ 每个 @Contended 字段前后填充 128 字节(典型缓存行大小 64~128B)。

⚠️ 注意事项:

  • 该注解属于 JDK 内部 API,不建议生产使用
  • 需添加 JVM 参数:-XX:-RestrictContended 才能生效
  • 可通过 -XX:ContendedPaddingWidth 调整填充大小

4.8 数组内存布局

数组对象头额外包含 4 字节长度字段。

boolean[] booleans = new boolean[3];
System.out.println(ClassLayout.parseInstance(booleans).toPrintable());

输出:

[Z object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 # mark
      4     4           (object header)                           00 00 00 00 # mark
      8     4           (object header)                           05 00 00 f8 # klass
     12     4           (object header)                           03 00 00 00 # array length
     16     3   boolean [Z.<elements>                             N/A
     19     5           (loss due to the next object alignment)
Instance size: 24 bytes

✅ 总头大小 16B(mark 8B + klass 4B + length 4B)。

4.9 压缩指针失效场景

当堆内存超过 32GB 或手动关闭压缩指针(-XX:-UseCompressedOops),Klass Word 变为 8 字节。

数组示例(关闭压缩指针):

[Z object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 # mark
      4     4           (object header)                           00 00 00 00 # mark
      8     4           (object header)                           28 60 d2 11 # klass
     12     4           (object header)                           01 00 00 00 # klass (高位)
     16     4           (object header)                           03 00 00 00 # length
     20     4           (alignment/padding gap)                  
     24     3   boolean [Z.<elements>                             N/A

✅ Klass 占用 8 字节(偏移 8~15),总对象头变为 20B。

5. 总结

本文系统解析了 HotSpot JVM 中对象与数组的内存布局机制,涵盖:

  • ✅ OOPs 结构与对象头组成
  • ✅ Mark Word 的多功能复用(哈希、锁、GC)
  • ✅ 字段重排序与内存对齐优化
  • @Contended 解决伪共享
  • ✅ 数组长度存储位置
  • ✅ 压缩指针的生效条件

🔧 进阶建议:

所有示例代码已托管至 GitHub:github.com/yourname/jvm-memory-layout-demo


原始标题:Memory Layout of Objects in Java