聊一聊JVM中对象的内存布局

126 阅读5分钟

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方可以评论,我们一起探讨!


Java对象内存布局深度解析


一、对象内存布局概览

Java对象在堆内存中的存储结构分为三个核心部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

以下为内存布局示意图:

|-------------------------|
|      对象头 (Header)      |
|-------------------------|
|  实例数据 (Instance Data) |
|-------------------------|
|   对齐填充 (Padding)      |
|-------------------------|

二、对象头(Header)

对象头包含两类信息:Mark Word类型指针
对于数组对象,对象头额外包含 数组长度字段

1. Mark Word
  • 作用:存储对象的运行时元数据,如哈希码、锁状态、GC分代年龄等。
  • 长度:64位系统下为 8字节(启用压缩指针时为 4字节)。
  • 内容动态变化:不同锁状态下,Mark Word的布局不同。
Mark Word在不同锁状态下的结构
|-------------------------------------------------------------|
| 锁状态        | 存储内容                                      |
|--------------|---------------------------------------------|
| 无锁          | 哈希码(25位) | 分代年龄(4位) | 偏向锁标志(0) | 锁标志(01) |
| 偏向锁        | 线程ID(54位) | 时间戳(2位)   | 分代年龄(4位) | 锁标志(01) |
| 轻量级锁      | 指向栈中锁记录的指针(62位)                   | 锁标志(00) |
| 重量级锁      | 指向互斥量(Monitor)的指针(62位)            | 锁标志(10) |
| GC标记        | 空(未使用)                                  | 锁标志(11) |
|-------------------------------------------------------------|
2. 类型指针(Class Pointer)
  • 作用:指向方法区中的类元数据(即 Class 对象),用于确定对象类型。
  • 长度
    • 未启用压缩指针:8字节(64位系统)。
    • 启用压缩指针(-XX:+UseCompressedOops):4字节
3. 数组长度(仅数组对象)
  • 长度:4字节(32位系统)或 8字节(64位系统,未启用压缩指针)。
  • 示例
    int[] arr = new int[10];  // 对象头包含数组长度字段(值为10)
    

三、实例数据(Instance Data)

  • 作用:存储对象的所有字段值(包括继承自父类的字段)。
  • 排列规则
    • 字段宽度对齐:相同宽度的字段分配在一起(如 longdouble 优先分配)。
    • 父类字段在前:父类定义的字段出现在子类字段之前。
示例:字段排列顺序
class Parent {
    int a;    // 4字节
    long b;   // 8字节
}

class Child extends Parent {
    int c;    // 4字节
    double d; // 8字节
}

// 内存布局:Parent.a → Parent.b → Child.c → Child.d

四、对齐填充(Padding)

  • 作用:确保对象总大小为 8字节的整数倍(HotSpot VM的内存对齐要求)。
  • 触发条件:对象头 + 实例数据的总长度不是8的倍数时自动填充。
  • 备注:这部分不一定存在,也没有什么特别的含义,仅仅是占位符,因为hotSpot要求对象起始地址都是8字节的整数倍,如果不是就对齐
示例:对齐填充计算

假设对象头12字节(Mark Word 8 + 类型指针4),实例数据17字节:
总长度 = 12 + 17 = 29 → 对齐后为32字节(填充3字节)。


五、内存布局优化技术

1. 压缩指针(Compressed OOPs)
  • 作用:将64位类型指针压缩为32位(节省内存)。
  • 条件:堆内存 ≤ 32GB(-Xmx32g)。
  • 启用参数-XX:+UseCompressedOops(默认开启)。
# 未启用压缩指针(64位)
对象头:Mark Word(8) + 类型指针(8) = 16字节

# 启用压缩指针(32位类型指针)
对象头:Mark Word(8) + 类型指针(4) = 12字节
2. 字段重排序(Field Reordering)
  • 作用:通过调整字段顺序减少内存填充,优化空间利用率。
  • 手动优化示例
    // 原始定义(填充4字节)
    class MyClass {
        byte a;     // 1
        int b;      // 4 → 需要3字节填充
        byte c;     // 1
    } // 总大小 = 1 + 4 + 1 + 3(填充) = 9 → 对齐到16字节
    
    // 优化后(无填充)
    class MyClassOptimized {
        int b;      // 4
        byte a;     // 1
        byte c;     // 1
    } // 总大小 = 4 + 1 + 1 + 2(填充) = 8 → 对齐到8字节
    

六、查看对象内存布局的工具

1. JOL(Java Object Layout)
  • 作用:分析对象内存布局的工具库。
  • 使用步骤
    1. 添加依赖:
      <dependency>
          <groupId>org.openjdk.jol</groupId>
          <artifactId>jol-core</artifactId>
          <version>0.16</version>
      </dependency>
      
    2. 打印对象布局:
      public class JOLDemo {
          public static void main(String[] args) {
              Object obj = new Object();
              System.out.println(ClassLayout.parseInstance(obj).toPrintable());
          }
      }
      
2. 输出示例
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

七、总结

组件内容优化技巧
对象头Mark Word + 类型指针(+ 数组长度)启用压缩指针减少类型指针大小
实例数据对象字段值(按宽度和继承顺序排列)手动调整字段顺序减少填充
对齐填充补齐至8字节倍数尽量让对象总大小为8的倍数以减少填充

关键点

  • 对象头是元数据存储的核心,Mark Word在不同锁状态下动态变化。
  • 实例数据的排列影响内存利用率,字段重排序可优化空间。
  • 对齐填充是内存对齐的必然结果,合理设计对象结构可减少填充开销。

通过工具(如JOL)分析对象布局,结合JVM参数调优(如压缩指针),可以显著提升内存使用效率。