要真正看懂Java锁膨胀源码,应聚焦对象头变化、线程竞争响应、锁状态迁移三大锚点,核心路径是轻量级锁通过ObjectSynchronizer::inflate()方法升级为重量级锁,关键在于Mark Word的CAS修改与ObjectMonitor的数据迁移。

要真正看懂 Java 锁膨胀的源码,不能从头到尾硬啃 HotSpot 全量 C++ 代码,而应聚焦关键路径、关键结构和状态流转逻辑。核心不在“读完”,而在“读准”——抓住对象头变化、线程竞争响应、锁状态迁移这三个锚点。
先明确阅读目标:不是理解所有分支,而是搞清“一个轻量级锁怎么变成重量级锁”这一条主干路径
HotSpot 中锁膨胀最核心的入口是 ObjectSynchronizer::inflate() 方法(位于 src/hotspot/share/runtime/synchronizer.cpp)。它被 monitorenter 字节码执行时调用,是整个锁升级的“决策中心”。
重点关注 inflate() 的四类状态处理逻辑
立即学习“Java免费学习笔记(深入)”;
- 当前 Mark Word 已指向 ObjectMonitor,说明已是重量级锁,直接返回 monitor
- Mark Word 是
INFLATING状态,表示其他线程正在执行膨胀,当前线程需自旋等待 - Mark Word 包含 locker(即轻量级锁),此时需:
• 分配新的ObjectMonitor实例
• 将原轻量级锁持有的线程 ID 和 displaced mark 搬移到 monitor 中
• 用 CAS 原子地把对象头从指向栈上 Lock Record 改为指向堆中 ObjectMonitor - Mark Word 处于无锁或偏向锁已撤销状态,则直接分配 monitor 并设置对象头
对照对象头(Mark Word)结构边读边验证
打开 markOop.hpp,看 markOopDesc 类定义,特别注意后 3 位(lock bits)和线程 ID、epoch、hash 等字段的复用关系。例如:
-
001→ 无锁(正常状态) -
101→ 偏向锁(JDK 15+ 默认禁用,但代码仍保留) -
00→ 轻量级锁(后两位为 00) -
11→ 重量级锁(后两位为 11)
每一步 inflate 操作,本质都是对 Mark Word 的一次 CAS 修改。建议配合 JOL(Java Object Layout)工具打印对象头十六进制值,在测试代码中触发不同阶段,再回查源码中对应位的判断条件。
跳过干扰项,直击关键数据迁移
膨胀过程最关键的“数据搬家”有两处:
-
monitor->set_header(dmw):把原来轻量级锁保存在栈帧 Lock Record 中的原始 Mark Word(displaced mark)写入 monitor -
monitor->set_owner(mark->locker()):把原轻量级锁持有线程设为 monitor 的 owner
这两步确保语义连续性:锁没丢,只是实现载体从线程栈换到了堆内存中的 Monitor。
配合字节码与运行时行为交叉印证
写一段极简 synchronized 竞争代码,用 javap -v 查看 monitorenter 指令位置;再用 -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 观察是否触发偏向撤销;最后加 -XX:+UnlockDiagnosticVMOptions -XX:+PrintBiasedLockingStatistics 看锁状态统计。源码逻辑必须和这些可观测行为对得上。
不复杂但容易忽略


















