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 调优技巧(如偏向锁延迟参数的调整)。

扩展工具

相关推荐
小吕学编程5 分钟前
Apache POI操作Excel详解
java·excel
Cynthia-石头19 分钟前
docker镜像下载到本地,并导入服务器
java·开发语言·eureka
Seven9727 分钟前
算法题:数组中的第k个最大元素
java·leetcode
huangyujun992012339 分钟前
设计模式杂谈-模板设计模式
java·设计模式
残*影1 小时前
Spring 中注入 Bean 有几种方式?
java·后端·spring
magic 2451 小时前
Java设计模式:责任链模式
java·设计模式·责任链模式
我是苏苏2 小时前
C#基础:使用线程池执行并行任务
java·服务器·c#
风象南2 小时前
SpringBoot实现实时弹幕
java·spring boot·后端
编程、小哥哥3 小时前
互联网大厂Java求职面试实战:Spring Boot微服务架构及Kafka消息处理示例解析
java·spring boot·微服务·kafka·面试技巧
magic 2454 小时前
return this;返回的是谁
java·开发语言