Java对象头信息

JDK1.8、64 位 HotSpot 虚拟机为标准,记录对象头信息的核心组成部分。

一、基础概述

1.1 定义

Java对象在堆内存中存储的完整结构分为三部分:对象头 + 实例数据 + 对齐填充
对象头是JVM管控对象的核心元数据区域,不存储任何业务属性,专门用于实现锁机制、GC分代、类识别等底层功能。

1.2 整体结构划分

对象类型 对象头组成 总大小(开启指针压缩)
普通对象 MarkWord + 类型指针 12字节
数组对象 MarkWord + 类型指针 + 数组长度 16字节

✅ 关键结论:无论对象处于什么锁状态,对象头的总大小固定不变,只是MarkWord内部的比特位会动态复用。

二、对象头详细组成

2.1 MarkWord(标记字)—— 核心重点

  • 大小:固定8字节(64bit)
  • 设计思想空间复用,不同状态下,相同的比特位存储不同的数据,最大限度节省内存。
  • 存储内容总览:哈希码、GC分代年龄、锁状态标记、偏向锁信息、锁指针。

64位MarkWord比特位布局(无锁状态)

1
2
3
| 未使用(25bit) | hashCode(31bit) | 未使用(1bit) | GC分代年龄(4bit) | 偏向位(1bit) | 锁标志位(2bit) |
|---------------|-----------------|--------------|------------------|--------------|----------------|
| 000...000     | 0x12345678      | 0            | 0000             | 0            | 01             |

2.2 Klass Pointer(类型指针)

  • 作用:指向元空间中该对象对应的类元数据,JVM通过这个指针识别对象所属的类,用于调用方法、访问类属性。
  • 大小
    • 开启指针压缩(默认):4字节
    • 关闭指针压缩:8字节
  • 指针压缩原理:JVM将堆内存按8字节对齐,指针只需要存储对齐后的偏移量,因此可以用32位表示4GB×8=32GB的堆内存。

2.3 数组长度(仅数组对象独有)

  • 大小:固定4字节
  • 作用:记录数组的元素个数。因为数组长度是动态的,无法从类元数据中获取,必须单独存储。
  • ✅ 易错点:普通对象没有这个字段,只有数组对象才有。

三、不同锁状态下MarkWord的区别 🔴

通过偏向位(1bit) + 锁标志位(2bit) 组合区分5种状态,MarkWord内部内容会动态变化:

锁状态 偏向位 锁标志位 MarkWord核心内容 使用场景 底层原理
无锁 0 01 hashCode(31bit) + GC分代年龄(4bit) 未加锁、无线程竞争 对象最原始状态
偏向锁 1 01 偏向线程ID(54bit) + 偏向时间戳 + GC分代年龄(4bit) 单线程反复加锁、无竞争 消除无竞争下的CAS开销,性能最高
轻量级锁 无意义 00 指向线程栈中锁记录(Lock Record)的指针 多线程交替执行、无并行竞争 CAS自旋抢锁,不阻塞线程
重量级锁 无意义 10 指向堆中ObjectMonitor监视器的指针 并发竞争激烈、自旋失败 依赖操作系统互斥量,线程阻塞挂起
GC标记 无意义 11 GC专用标记位 垃圾回收阶段 标记待回收对象,业务不使用

锁状态核心规律

  1. 锁升级不可逆:无锁 → 偏向锁 → 轻量级锁 → 重量级锁,只能升级不能降级。
  2. 只有无锁和偏向锁存储GC分代年龄,轻量级/重量级锁状态下,MarkWord全部用来存储指针。
  3. 偏向锁与hashCode冲突:一旦调用对象的hashCode()方法,偏向锁会立即撤销,因为偏向锁状态下没有空间存储hashCode。

四、对象完整内存布局

4.1 三部分详解

  1. 对象头:存储JVM管控元数据(锁、GC、类信息),大小固定。
  2. 实例数据:存储类的成员变量(基本数据类型、对象引用),按继承关系和字节对齐规则排列。
  3. 对齐填充:无业务含义,仅用于内存对齐。HotSpot规定对象整体大小必须是8字节的整数倍,不足则补位填充。

4.2 对象内存计算示例

1
2
3
4
5
6
// 计算以下对象的内存大小
class User {
    private int id;       // 4字节
    private String name;  // 4字节(引用类型,开启指针压缩)
    private boolean flag; // 1字节
}

计算过程:

  • 对象头:12字节
  • 实例数据:4 + 4 + 1 = 9字节
  • 对齐填充:12 + 9 = 21字节,不是8的整数倍,补3字节
  • 总大小:24字节

五、总结

  1. ✅ GC分代年龄最大为15,因为只占用4个比特位(2⁴-1=15)。
  2. ✅ 开启指针压缩时,普通对象头12字节,数组对象头16字节。
  3. ✅ 调用hashCode()会导致偏向锁撤销,因为偏向锁状态下无法存储hashCode。
  4. ✅ 轻量级锁的锁记录存储在线程栈中,不是堆内存。
  5. ✅ 重量级锁的ObjectMonitor对象存储在堆内存中。
  6. ❌ 错误:对象头大小会随锁状态变化。
  7. ❌ 错误:所有对象都有数组长度字段。
  8. ❌ 错误:锁可以降级。