JVM锁升级路径严格为无锁→偏向锁→轻量级锁→重量级锁,不可跳过轻量级锁阶段;所谓“秒升”实为轻量级锁尝试初期因自旋失败、持有线程阻塞、临界区耗时长、系统负载高或hashCode调用等触发快速膨胀,仍属该阶段内决策。

JVM 不会跳过轻量级锁阶段直接从偏向锁升级为重量级锁。锁膨胀路径是严格单向且固定的:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。所谓“跳过轻量级锁”,实际是指在**轻量级锁尝试阶段尚未真正建立锁记录或完成 CAS 操作前,就因特定高竞争信号而提前触发重量级锁膨胀**——这仍属于轻量级锁流程内的快速升级决策,而非绕过该阶段。
哪些情况会让 JVM 在轻量级锁阶段“秒升”重量级锁
当 JVM 判断当前锁竞争已超出轻量级锁的适用边界,它会在轻量级锁获取尝试初期就放弃自旋策略,直接走 Monitor 分配与阻塞流程。关键触发条件包括:
- 连续自旋失败且历史成功率极低:例如某锁在过去多次尝试中,自旋从未成功获取过;JVM 的自适应逻辑会将本次自旋次数设为 0,CAS 失败后立即升级
- 持有锁线程已进入阻塞态(如 sleep、wait、IO 等):JVM 可通过线程状态判断锁释放遥遥无期,继续自旋纯属空耗 CPU,直接升级更合理
- 临界区执行时间被 JVM 动态识别为“长耗时”:比如同步块内调用了 native 方法、反射、或已观测到平均执行超过数百纳秒,JVM 认为自旋收益趋近于零
- 系统负载过高或 CPU 资源紧张:JVM 会参考全局指标(如最近 GC 频率、系统平均负载),若判断自旋加剧调度压力,则倾向快速退让给 OS 调度
hashCode 调用导致的“伪跳过”现象
这是最容易被误解为“跳过轻量级锁”的典型场景:
- 一个对象处于偏向锁状态,此时任意线程调用其
hashCode() - JVM 必须将 hashCode 存入对象头,但偏向锁的 Mark Word 没有预留空间
- 于是直接撤销偏向锁,并一步到位升级为重量级锁(因为 Monitor 结构天然支持存储 hashCode)
- 注意:这并非跳过轻量级锁,而是偏向锁撤销后不经过轻量级锁阶段,直接走重量级锁初始化路径——因为轻量级锁本身也不支持存 hashCode,它同样需要 Monitor
如何验证是否发生了快速膨胀
可通过 JVM 参数开启锁相关日志观察真实行为:
-
-XX:+PrintBiasedLockingStatistics -XX:+TraceClassLoading查看偏向撤销统计 -
-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=vm.log配合-XX:+TraceMonitorInflation输出每次锁膨胀的时机、原因和目标状态 - 重点关注日志中类似
Inflating object ... to heavy weight的记录,其前序状态通常是biased或lightweight,极少出现跨级标记
对开发的实际影响
这类快速升级虽由 JVM 自动决策,但暴露了代码中潜在的并发瓶颈:
- 高频调用
hashCode()或wait()/notify()的同步对象,应避免长期持有偏向锁 - 临界区含远程调用、磁盘读写、复杂计算时,synchronized 不再是最优选择,可考虑
ReentrantLock+ 超时机制 - 若监控发现大量锁在轻量级阶段即膨胀,说明竞争已常态化,需从架构上减少共享状态或改用无锁数据结构










