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)和字段在源码中定义顺序的影响。通常的排列规则是:
- 相同宽度的字段会被分配在一起。
- 父类定义的变量会优先于子类。
- 如果 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 等工具进行分析。