导读:本期聚焦于星宫一花创作的《如何通过 JVM 对象头 Mark Word 的动态位偏移理解重量级锁下的 ObjectMonitor 关联》,敬请观看详情。当一个 Java 对象升级为重量级锁后,对象头里的 Mark Word 到底存了什么?它又是如何指向 ObjectMonitor 的?不少研究并发底层的人在读完 synchronized 的锁升级流程后,仍对最后一步的指针关联感到困惑。本文从 64 位 JVM 的 Mark Word 位布局入手,逐位拆解 tag bits 与锁标志位的组合含义,解释指针压缩开启与否对指针字段宽度的实际影响,再结合 hotspot 源码中 ObjectMonitor 的结构,说明对象头中的指针如何完成从栈帧到监视器的寻址闭环,最后分析锁膨胀、锁撤销与锁重入在该机制下的执行路径,帮助读者把碎片知识串成一条可验证的主线。

研究 synchronized 的底层实现时,几乎所有人都会在同一个地方卡住:对象已经膨胀为重量级锁,Mark Word 里那串指向 ObjectMonitor 的指针到底是怎么来的,又是怎么被读取的。要彻底搞懂这个问题,不能停留在“锁标志位是 10”这种结论层面,必须回到 64 位 HotSpot 的位布局本身,理解 tag bits 的动态偏移逻辑,才能明白指针字段的边界在哪里、为什么开启指针压缩后行为会变化。

如何通过 JVM 对象头 Mark Word 的动态位偏移理解重量级锁下的 ObjectMonitor 关联

一、64 位 Mark Word 的位布局与动态偏移的含义

在 64 位 JVM 上,一个对象头的 Mark Word 占 8 个字节,共 64 个 bit。这 64 个 bit 并不是固定划分的,而是根据低位的锁标志位动态解释剩余字段的含义。最后 2 个 bit 是锁标志位(lock bits),倒数第 3 个 bit 是 biased_lock 位,这两部分合起来构成了对象处于哪种状态的解释依据。

为什么说是动态偏移?因为不同状态下,剩余 61 个 bit 的字段划分完全不同。无锁状态下,前面 25 个 bit 未使用,接着 31 个 bit 存 identity_hashcode,再往后是分代年龄。而在轻量级锁状态下,高 62 个 bit 全部变成了指向栈中 Lock Record 的指针。到了重量级锁,高 62 个 bit 则指向堆中的 ObjectMonitor 对象。同一个物理位置,在不同状态下被解释成完全不同的字段,这就是“动态位偏移”的本质。

关键在于代码里并不存在“从第几位到第几位是某字段”的硬编码偏移。HotSpot 通过位运算和常量掩码来提取字段,比如判断是否为重量级锁,就是检查 (mark & 0b111) == 0b010。锁标志为 10 时,JVM 知道这 62 个 bit 是一个指针,直接把 Mark Word 右移后强转即可拿到 ObjectMonitor 的地址。理解了这一点,后续的关联机制就顺理成章了。

二、重量级锁的膨胀过程与 ObjectMonitor 的分配时机

重量级锁不是凭空出现的。当轻量级锁在自旋失败或发生锁竞争后,会触发锁膨胀(inflation)。膨胀过程发生在 ObjectSynchronizer::inflate 方法中,它的工作可以概括为几步:首先通过 CAS 或原子操作在堆上分配一个 ObjectMonitor 对象;然后把这个 ObjectMonitor 的地址写入 Mark Word 的高 62 位,同时把锁标志位设置为 10;最后如果有线程正在持有该轻量级锁,还会把这个线程包装成一个 ObjectWaiter 挂到 monitor 的等待队列里。

这里有一个容易被忽略的细节:ObjectMonitor 的分配是懒加载式的。JVM 维护着一个全局的 monitor 监视池(ObjectMonitorFreeList),膨胀时优先从空闲链表中取一个复用,取不到才真正 new 一个。这也是为什么频繁的锁膨胀不会立刻把堆撑爆——监视器对象是可以回收再分配的。只有当膨胀失败(哈希表打满)时才会抛出 OutOfMemoryError。

膨胀成功之后,对象头与 ObjectMonitor 的关联就建立起来了。此后任何线程走到 monitorenter 字节码指令,解释器或 JIT 生成的代码都会先读对象头,判断锁标志位是 10,然后从 Mark Word 中解析出 monitor 指针,接着进入 ObjectMonitor::enter 的慢路径逻辑。

三、ObjectMonitor 内部结构与关联后的执行路径

拿到 monitor 指针之后,竞争就完全发生在 ObjectMonitor 内部了。它的核心字段可以简化成下面的结构:

// HotSpot 中 ObjectMonitor 的关键字段(简化)
class ObjectMonitor {
  void* volatile _owner;      // 持有锁的线程或 BasicLock
  volatile intptr_t _recursions; // 重入计数
  ObjectWaiter* volatile _EntryList; // 阻塞队列(竞争失败者)
  ObjectWaiter* volatile _cxq;      // 最近到达的竞争队列
  Thread* volatile _succ;           // 假定的继任者(减少不必要的唤醒)
  volatile int _WaitSetLock;        // 保护 _WaitSet 的自旋锁
  ObjectWaiter* volatile _WaitSet;  // wait() 等待队列
  volatile markWord _header;        // 保存膨胀前的 Mark Word
};

其中 _recursions 字段解释了可重入性:同一个线程再次 enter 时,发现 _owner 就是自己,计数加一,直接返回,不需要再走队列。_header 字段则保存了膨胀前的 Mark Word 快照,这非常关键——锁释放后对象要回到无锁或轻量级状态,靠的就是这份备份。如果膨胀前对象已经计算过 identity_hashcode,这个值也被记录在 _header 里,避免锁状态切换导致哈希码丢失。

竞争失败的线程会经历一个精心设计的路径:先尝试 CAS 进入 _cxq 队列头部,然后执行若干次自旋(自适应自旋),期待持有者很快释放;自旋失败后才调用 pthread 的条件变量真正挂起线程,这也是重量级锁开销大的根源——涉及内核态的上下文切换。唤醒时线程也不是直接获得锁,而是从 _cxq_EntryList 中被移出后重新竞争,体现了非公平锁的语义。

四、用实验验证 Mark Word 与 monitor 的关联

理解了原理,最好亲手验证。JOL(Java Object Layout)是观察对象头布局最方便的工具。写一段代码制造重量级锁,然后打印对象头:

import org.openjdk.jol.info.ClassLayout;

public class HeavyLockDemo {
    static final Object lock = new Object();

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                try { Thread.sleep(2000); } catch (Exception e) {}
            }
        });
        t1.start();
        Thread.sleep(500); // 确保 t1 已进入同步块

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t2 got lock");
            }
        });
        t2.start();
        t2.join();

        // 输出 Mark Word,观察锁标志位
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }
}

运行后观察输出的 Mark Word 行,当锁竞争发生并膨胀后,最后 3 位会呈现 010 的组合(10 为重量级锁标志,biased_lock 位为 0),而前面的十六进制数值就是 ObjectMonitor 在堆中的地址。你可以在不同运行间对比这个值,会发现同一把锁在多次膨胀中地址可能不同——这正是监视池复用机制的表现。

另外可以对比开启与关闭指针压缩(-XX:-UseCompressedOops)时的输出。理论上重量级锁指针占用的是 Mark Word 的高 62 位,与压缩指针无关,因为压缩Oops机制只作用于对象引用字段,而 monitor 指针是原生地址直接存储的。亲自做一次对比实验,能帮助你彻底区分“对象引用的压缩”和“Mark Word 内部指针的存储方式”这两个容易混淆的概念,把对 Mark Word 动态布局的理解落到可观察的证据上。

Mark Word重量级锁ObjectMonitor修改时间:2026-09-04 17:48:47

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260904/50386.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。