轻量级锁在自旋失败后直接膨胀为重量级锁,不依赖自旋次数阈值,而是依据是否有其他线程等待及mark word状态判断;膨胀时创建objectmonitor并更新对象头标志位为10。

Java 中轻量级锁在自旋失败后,并不会“自动升级为重量级锁”,而是直接膨胀(inflate)为重量级锁——这个过程不依赖自旋次数阈值,而取决于是否已有其他线程在等待该锁。
轻量级锁的膨胀触发条件
当一个线程尝试获取已被其他线程持有的轻量级锁时:
- 若持有线程仍在运行且未释放锁,当前线程会先尝试自旋(默认关闭,需显式开启
-XX:+UseSpinning,JDK 6 后默认禁用); - 但无论是否自旋、自旋多少次,只要发现锁对象的 Mark Word 中已记录有竞争(即锁标志位为 00,但偏向位为 0,且指向栈中 Lock Record 的指针已失效或被替换),JVM 就会立即触发锁膨胀;
- 关键判断依据是:当前线程发现无法通过 CAS 替换 Mark Word 成功,且原 Mark Word 中存储的是持有线程的栈帧中的锁记录地址(说明是轻量级锁状态),但该地址已无效(如持有线程已退出同步块或发生锁重入后升级),此时 JVM 会调用
ObjectSynchronizer::inflate进行膨胀。
膨胀过程的核心步骤
膨胀本质是创建一个 Monitor 对象(C++ 层的 ObjectMonitor),并将其地址写入对象头:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- JVM 在堆上分配一个
ObjectMonitor结构体,包含 _owner、_waitSet、_entryList 等字段; - 通过 CAS 将对象头的 Mark Word 更新为指向该 Monitor 的指针,同时设置锁标志位为 10(重量级锁标识);
- 原轻量级锁所关联的栈中 Lock Record 被废弃,后续所有对该对象的加锁/解锁操作都走 Monitor 的 enter/exit 流程;
- 如果此时有线程正在自旋等待,它会在下一次检查对象头时发现已是重量级锁状态,随即进入 Monitor 的 _entryList 阻塞排队。
自旋与升级没有强绑定关系
需要澄清一个常见误解:JDK 并未设计“自旋 N 次失败后升级”的策略。实际行为是:
- 自旋只是轻量级锁竞争时的一种优化尝试,仅发生在多核且临界区极短的场景下;
- 一旦 CAS 获取锁失败,且检测到锁处于轻量级状态但已不可重入抢占(例如持有线程正在执行、或已开始解锁流程),JVM 会跳过进一步自旋,直接膨胀;
- 从 JDK 6 Update 23 起,
-XX:+UseSpinning默认关闭,意味着绝大多数情况下,轻量级锁竞争会直接膨胀,不经历自旋阶段。
如何验证锁膨胀行为
可通过 JVM 参数和工具观察:
- 添加
-XX:+PrintGCDetails -XX:+UnlockDiagnosticVMOptions -XX:+PrintBiasedLockingStatistics辅助分析; - 使用 JOL(Java Object Layout)查看对象头变化:
new Object()在不同锁状态下 Mark Word 的二进制值; - 配合 async-profiler 或 JFR(Java Flight Recorder)捕获锁事件,识别 inflate 调用栈。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










