Mark Word 深度解析:对象头的“密码本”

一、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 的指针。

三、关键设计细节

  1. 哈希码的延迟计算

    • 对象创建时不会立即计算哈希码,首次调用 hashCode() 时生成并写入 Mark Word。
    • 若对象已处于偏向锁或轻量级锁状态,哈希码会丢失(需撤销锁状态)。
  2. 偏向锁的 epoch 机制

    • epoch 用于批量撤销偏向锁。当某一类对象的偏向锁被频繁撤销时,JVM 会递增 epoch,使旧的偏向锁失效,避免全局撤销。
  3. 指针压缩优化

    • 在 64 位 JVM 开启压缩指针(默认开启)时,类型指针仅占 4 字节,Mark Word 仍为 8 字节。

四、实际应用中的问题

  1. 哈希码与锁的冲突

    • 若对象已计算哈希码,则无法进入偏向锁状态(哈希码占用 Mark Word 空间)。
    • 代码中过早调用 hashCode() 可能导致偏向锁失效。
  2. 偏向锁的性能陷阱

    • 高竞争场景下,偏向锁的撤销和升级会增加开销(JDK 15 后默认禁用偏向锁)。
  3. 调试技巧

    • 使用 jol-core 分析对象头,快速定位锁状态问题。
    • 通过 -XX:+PrintBiasedLockingStatistics 参数打印偏向锁统计信息。

总结

Mark Word 是 JVM 同步机制的基石,其动态位模式设计实现了锁状态的灵活切换。理解 Mark Word 的细节,能够:

  1. 优化高并发代码(如避免哈希码与偏向锁冲突)。
  2. 诊断多线程问题(如通过对象头分析锁竞争)。
  3. 掌握 JVM 调优技巧(如偏向锁延迟参数的调整)。

扩展工具

相关推荐
Java中文社群20 分钟前
有点意思!Java8后最有用新特性排行榜!
java·后端·面试
代码匠心28 分钟前
从零开始学Flink:数据源
java·大数据·后端·flink
间彧33 分钟前
Spring Boot项目中如何自定义线程池
java
间彧1 小时前
Java线程池详解与实战指南
java
用户298698530141 小时前
Java 使用 Spire.PDF 将PDF文档转换为Word格式
java·后端
渣哥1 小时前
ConcurrentHashMap 1.7 vs 1.8:分段锁到 CAS+红黑树的演进与性能差异
java
间彧1 小时前
复用线程:原理详解与实战应用
java
咖啡Beans3 小时前
使用OpenFeign实现微服务间通信
java·spring cloud
我不是混子3 小时前
说说单例模式
java