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

扩展工具

相关推荐
空空kkk3 分钟前
MyBatis——代理Dao方式的增删改查操作
java·数据库·mybatis
Seven9713 分钟前
线性数据结构
java
带刺的坐椅16 分钟前
Solon 不依赖 Java EE 是其最有价值的设计!
java·spring·web·solon·javaee
青云交19 分钟前
Java 大视界 -- 基于 Java 的大数据分布式存储在数字媒体内容存储与版权保护中的应用
java·性能优化·区块链·分布式存储·版权保护·数字媒体·ai 识别
踢球的打工仔36 分钟前
PHP面向对象(5)
android·java·php
Rover.x38 分钟前
错误:找不到或无法加载主类 @C:\Users\AppData\Local\Temp\idea_arg_file223456232
java·ide·intellij-idea
4***172740 分钟前
使用 java -jar 命令启动 Spring Boot 应用时,指定特定的配置文件的几种实现方式
java·spring boot·jar
CoderYanger1 小时前
优选算法-字符串:63.二进制求和
java·开发语言·算法·leetcode·职场和发展·1024程序员节
3***31211 小时前
java进阶1——JVM
java·开发语言·jvm
FeiHuo565151 小时前
微信个人号开发中如何高效实现API二次开发
java·开发语言·python·微信