基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

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(2412^4 - 1)。
    • 是否偏向锁:1 bit,值为 0。
    • 锁标志位:2 bit,值为 01
  • 偏向锁状态(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 锁的升级机制、对象的垃圾回收年龄控制、基于多态的方法动态分派,以及反射等底层机制。

00:00
00:00