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比特位布局(无锁状态)
|
|
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专用标记位 | 垃圾回收阶段 | 标记待回收对象,业务不使用 |
锁状态核心规律
- 锁升级不可逆:无锁 → 偏向锁 → 轻量级锁 → 重量级锁,只能升级不能降级。
- 只有无锁和偏向锁存储GC分代年龄,轻量级/重量级锁状态下,MarkWord全部用来存储指针。
- 偏向锁与hashCode冲突:一旦调用对象的
hashCode()方法,偏向锁会立即撤销,因为偏向锁状态下没有空间存储hashCode。
四、对象完整内存布局
4.1 三部分详解
- 对象头:存储JVM管控元数据(锁、GC、类信息),大小固定。
- 实例数据:存储类的成员变量(基本数据类型、对象引用),按继承关系和字节对齐规则排列。
- 对齐填充:无业务含义,仅用于内存对齐。HotSpot规定对象整体大小必须是8字节的整数倍,不足则补位填充。
4.2 对象内存计算示例
|
|
计算过程:
- 对象头:12字节
- 实例数据:4 + 4 + 1 = 9字节
- 对齐填充:12 + 9 = 21字节,不是8的整数倍,补3字节
- 总大小:24字节
五、总结
- ✅ GC分代年龄最大为15,因为只占用4个比特位(2⁴-1=15)。
- ✅ 开启指针压缩时,普通对象头12字节,数组对象头16字节。
- ✅ 调用
hashCode()会导致偏向锁撤销,因为偏向锁状态下无法存储hashCode。 - ✅ 轻量级锁的锁记录存储在线程栈中,不是堆内存。
- ✅ 重量级锁的ObjectMonitor对象存储在堆内存中。
- ❌ 错误:对象头大小会随锁状态变化。
- ❌ 错误:所有对象都有数组长度字段。
- ❌ 错误:锁可以降级。