一、Mark Word 的内存布局
Mark Word 在 64 位 JVM 中占 8 字节(64 bits),其内容根据对象状态动态变化,具体分为以下 5 种模式:
锁状态 | 存储内容 | 位分布(64 bits) |
---|---|---|
无锁 | 哈希码(31 bits) + 分代年龄(4 bits) + 偏向模式(1 bit) + 锁标志位(2 bits) | `unused:25 |
偏向锁 | 线程ID(54 bits) + 时间戳(epoch,2 bits) + 分代年龄(4 bits) + 锁标志位 | `thread:54 |
轻量级锁 | 指向栈中锁记录(Lock Record)的指针(62 bits,高位为 0) + 锁标志位 | `ptr_to_lock_record:62 |
重量级锁 | 指向 Monitor 对象(ObjectMonitor)的指针(62 bits,高位为 0) + 锁标志位 | `ptr_to_monitor:62 |
GC 标记 | 用于垃圾回收的标记信息(如对象存活状态) | `未使用:62 |
关键字段解释:
- 锁标志位(2 bits):标识当前锁状态(01=无锁/偏向锁,00=轻量级锁,10=重量级锁,11=GC标记)。
- 偏向模式(1 bit):仅当锁标志位为 01 时有效,1 表示偏向锁,0 表示无锁。
- 分代年龄(4 bits):记录对象经历过的 GC 次数(最大值 15,触发后晋升老年代)。
- 哈希码(31 bits) :延迟计算,首次调用
hashCode()
时生成。 - 线程ID(54 bits):偏向锁持有者的线程 ID(实际是线程指针的哈希值)。
二、Mark Word 的动态变化
通过一个简单示例观察 Mark Word 的变化(使用 JOL 工具):
java
// 添加依赖:org.openjdk.jol:jol-core
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
1. 初始状态(无锁)
输出结果:
css
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) 01 00 00 00 # Mark Word(01 表示无锁)
4 4 (object header) 00 00 00 00 # 类型指针(压缩后)
8 4 (object header) e5 01 00 00 # 数组长度(非数组对象为 0)
12 4 (loss due to the next object alignment)
- 此时锁标志位为
01
,偏向模式为0
(无锁)。
2. 偏向锁状态
当线程首次获取偏向锁时,Mark Word 会记录线程 ID 和 epoch:
java
synchronized (obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
输出结果:
css
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) 05 e0 00 20 # Mark Word(偏向锁,线程ID、epoch)
4 4 (object header) 00 00 00 00
8 4 (object header) e5 01 00 00
12 4 (loss due to the next object alignment)
- 锁标志位仍为
01
,但偏向模式位变为1
。 - 线程ID 和 epoch 被写入高位。
3. 轻量级锁状态
当第二个线程尝试获取锁时,偏向锁撤销,升级为轻量级锁:
java
Thread t1 = new Thread(() -> {
synchronized (obj) { /* ... */ }
});
Thread t2 = new Thread(() -> {
synchronized (obj) { /* ... */ }
});
t1.start(); t1.join(); // 触发偏向锁
t2.start(); t2.join(); // 触发轻量级锁
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
输出结果:
css
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) f0 f8 7f 02 # 指向栈中锁记录的指针(00 结尾)
4 4 (object header) 00 00 00 00
8 4 (object header) e5 01 00 00
12 4 (loss due to the next object alignment)
- 锁标志位变为
00
,表示轻量级锁。 - Mark Word 存储指向线程栈中锁记录的指针。
4. 重量级锁状态
当竞争激烈时,升级为重量级锁:
java
for (int i = 0; i < 100; i++) {
new Thread(() -> {
synchronized (obj) { /* 模拟高竞争 */ }
}).start();
}
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
输出结果:
css
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) 5a 3d 01 00 # 指向 ObjectMonitor 的指针(10 结尾)
4 4 (object header) 00 00 00 00
8 4 (object header) e5 01 00 00
12 4 (loss due to the next object alignment)
- 锁标志位变为
10
,表示重量级锁。 - Mark Word 存储指向 ObjectMonitor 的指针。
三、关键设计细节
-
哈希码的延迟计算
- 对象创建时不会立即计算哈希码,首次调用
hashCode()
时生成并写入 Mark Word。 - 若对象已处于偏向锁或轻量级锁状态,哈希码会丢失(需撤销锁状态)。
- 对象创建时不会立即计算哈希码,首次调用
-
偏向锁的 epoch 机制
- epoch 用于批量撤销偏向锁。当某一类对象的偏向锁被频繁撤销时,JVM 会递增 epoch,使旧的偏向锁失效,避免全局撤销。
-
指针压缩优化
- 在 64 位 JVM 开启压缩指针(默认开启)时,类型指针仅占 4 字节,Mark Word 仍为 8 字节。
四、实际应用中的问题
-
哈希码与锁的冲突
- 若对象已计算哈希码,则无法进入偏向锁状态(哈希码占用 Mark Word 空间)。
- 代码中过早调用
hashCode()
可能导致偏向锁失效。
-
偏向锁的性能陷阱
- 高竞争场景下,偏向锁的撤销和升级会增加开销(JDK 15 后默认禁用偏向锁)。
-
调试技巧
- 使用
jol-core
分析对象头,快速定位锁状态问题。 - 通过
-XX:+PrintBiasedLockingStatistics
参数打印偏向锁统计信息。
- 使用
总结
Mark Word 是 JVM 同步机制的基石,其动态位模式设计实现了锁状态的灵活切换。理解 Mark Word 的细节,能够:
- 优化高并发代码(如避免哈希码与偏向锁冲突)。
- 诊断多线程问题(如通过对象头分析锁竞争)。
- 掌握 JVM 调优技巧(如偏向锁延迟参数的调整)。
扩展工具:
- JOL 工具:分析对象内存布局。
- Hotspot 源码:搜索
markOop.hpp
查看 Mark Word 定义。