Java 对象头里具体包含哪些信息?
在 Java 虚拟机(以最常用的 HotSpot JVM 为例)中,对象在内存中的布局分为三个部分:对象头(Object Header)、实例数据(Instance Data) 和 对齐填充(Padding)。
你所问的对象头(Object Header),主要包含以下两到三个部分的信息:
1. Mark Word(标记字段)
这是对象头中最复杂、包含信息最多的一部分。它主要用来存储对象自身的运行时数据。
为了在极小的空间内存下存储尽量多的信息,Mark Word 被设计成一个非固定的动态数据结构,它会根据对象当前的状态复用自己的存储空间。
在 64位 JVM 中,Mark Word 占 8 个字节(64 bit)。根据对象锁状态的不同,它包含的具体信息会发生动态变化,主要有以下五种状态:
- 无锁状态(Normal / Unlocked):
- 对象的 HashCode(注意:这里存的是未重写的原始 IdentityHashCode,一旦调用过系统的
hashCode()方法计算后就会存在这里)。 - 分代年龄(GC Age):占 4 bit,因此 Java 对象默认晋升老年代的年龄最大是 15()。
- 是否偏向锁:1 bit,值为 0。
- 锁标志位:2 bit,值为 01。
- 对象的 HashCode(注意:这里存的是未重写的原始 IdentityHashCode,一旦调用过系统的
- 偏向锁状态(Biased Lock):
- 线程 ID(Thread ID):记录持有偏向锁的线程 ID。
- Epoch:偏向时间戳,用于偏向锁的批量重偏向/批量撤销。
- 分代年龄(GC Age)。
- 是否偏向锁:1 bit,值为 1。
- 锁标志位:2 bit,值为 01。
- 轻量级锁状态(Lightweight Lock):
- 不再记录 HashCode 和年龄。
- 指向栈中锁记录的指针(Pointer to Lock Record):占 62 bit,指向当前拥有该锁的线程栈中的
Lock Record。 - 锁标志位:2 bit,值为 00。
- 重量级锁状态(Heavyweight Lock):
- 指向重量级锁(Monitor,即管程)的指针:占 62 bit,指向 ObjectMonitor 对象(这也是为什么 Java 中任何对象都可以作为锁的原因)。
- 锁标志位:2 bit,值为 10。
- GC 标记状态(GC Marked):
- 主要在垃圾回收时使用,通常存储空或者Forwarding Pointer(转发指针)。
- 锁标志位:2 bit,值为 11。
2. Klass Pointer(类型指针)
这部分用于指向对象所属的类元数据(Class Metadata)。
- 作用:JVM 通过这个指针来确定这个对象是哪个类的实例。这个指针指向方法区(JDK 8+ 称为元空间 Metaspace)中的类信息。
- 占用空间:
- 在 64位 JVM 中,原本应该占 8 个字节(64 bit)。
- 但默认情况下,JVM 会开启指针压缩(
-XX:+UseCompressedClassPointers),压缩后它只占 4 个字节(32 bit)。
3. Array Length(数组长度)—— 仅数组对象才有
如果当前对象是一个Java数组,那么对象头中还会多出一块记录数组长度的数据。
- 作用:因为普通 Java 对象的大小可以通过元数据信息确定,但数组的长度是动态的,JVM 无法通过数组类的元数据推断出数组的大小,所以必须在对象头中显式记录。
- 占用空间:占 4 个字节(32 bit)。
总结与空间占用 (64位 JVM, 开启指针压缩)
- 普通对象 的对象头 = Mark Word (8字节) + Klass Pointer (4字节) = 12字节。(由于 Java 对象要求 8 字节对齐,所以还会产生 4 字节的 Padding,整体表现为 16 字节)。
- 数组对象 的对象头 = Mark Word (8字节) + Klass Pointer (4字节) + Array Length (4字节) = 16字节。
正是因为有了对象头中的这些信息,Java 才能实现诸如 synchronized 锁的升级机制、对象的垃圾回收年龄控制、基于多态的方法动态分派,以及反射等底层机制。
右滑查看面试常问