java对象在内存中的布局

26 阅读2分钟

java对象在内存中的布局是什么样的

在 HotSpot 虚拟机里,Java 对象在内存中的布局主要分为三个部分,下面为你详细介绍:

对象头(Object Header)

对象头由标记字(Mark Word)和类元数据指针(Klass Pointer)构成。

  • 标记字(Mark Word) :这部分用于存储对象自身的运行时数据,例如哈希码、GC 分代年龄、锁状态标志等。在 32 位和 64 位虚拟机中,其长度分别为 32bit 和 64bit。就以 64 位虚拟机来说,它的结构大致如下:

    plaintext

    |-------------------------------------------------------|--------------------|
    |                  Mark Word (64 bits)                 |       State        |
    |-------------------------------------------------------|--------------------|
    | unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | lock:01 | Normal |
    |-------------------------------------------------------|--------------------|
    | thread:54 | epoch:2     | unused:1 | age:4 | biased_lock:1 | lock:01 | Biased |
    |-------------------------------------------------------|--------------------|
    |                     ptr_to_lock_record:62            | lock:00 | Lightweight Locked |
    |-------------------------------------------------------|--------------------|
    |                     ptr_to_heavyweight_monitor:62    | lock:10 | Heavyweight Locked |
    |-------------------------------------------------------|--------------------|
    |                                              | lock:11 | Marked for GC    |
    |-------------------------------------------------------|--------------------|
    
  • 类元数据指针(Klass Pointer) :该指针指向对象所属类的元数据,虚拟机能够通过这个指针确定该对象是哪个类的实例。在 64 位虚拟机中,启用指针压缩时为 32 位,未启用时为 64 位。

实例数据(Instance Data)

实例数据用于存放对象的属性信息,涵盖父类继承和子类定义的属性。在内存中,这些数据会按照一定规则进行排列,排列顺序受虚拟机参数(FieldsAllocationStyle)和字段在源码中定义顺序的影响。通常的排列规则是:

  1. 相同宽度的字段会被分配在一起。
  2. 父类定义的变量会优先于子类。
  3. 如果 CompactFields 参数开启,子类中较窄的变量可能会插入到父类变量的空隙中。

对齐填充(Padding)

由于 HotSpot 虚拟机要求对象起始地址必须是 8 字节的整数倍,当对象头和实例数据的总长度不是 8 字节的整数倍时,就需要通过对齐填充来补全。

示例

下面通过一个简单的 Java 类示例,来看看对象在内存中的布局情况:

java

public class ExampleObject {
    private int id;         // 4字节
    private String name;    // 引用类型,默认开启指针压缩后为4字节
    private boolean flag;   // 1字节
    
    // 假设对象头占12字节(8字节标记字 + 4字节类指针)
    // 实例数据:4(id) + 4(name) + 1(flag) = 9字节
    // 总大小:12(对象头) + 9(实例数据) = 21字节
    // 对齐填充:24(8的倍数) - 21 = 3字节
    // 最终对象大小:24字节
}

总结

Java 对象在内存中的布局由对象头、实例数据和对齐填充三部分组成,这种设计既保证了对象的高效访问,又实现了内存的优化利用。如果你想获取对象的精确内存布局,可以借助 jol-core 等工具进行分析。