JDK 15+ 默认禁用偏向锁,JDK 17+ 彻底移除;其撤销需全局安全点(STW),导致毫秒级延迟,故现代 JVM 直接走无锁→轻量级锁→重量级锁路径,重点优化自适应自旋。

偏向锁在现代多核 JVM 中已不是“要不要用”的问题,而是“它根本不存在了”——JDK 21+ 的 HotSpot 里,BiasedLocking 相关逻辑已被彻底移除;JDK 15 起默认禁用,JDK 20 彻底删除。所谓“撤销代价分析”,本质是理解 JVM 为何放弃它。
为什么一次 biased lock revoke 会触发 STW?
撤销偏向锁不能在任意时刻执行:它要修改所有线程共享的锁对象头(Mark Word),还可能涉及线程栈中已缓存的偏向状态。JVM 必须等所有 Java 线程都到达安全点(Safepoint)才能操作——这导致全局暂停(Stop-The-World)。哪怕只撤销一个锁,只要线程没停稳,就得等。
- 常见错误现象:
VMThread在 GC 日志或jstack -l输出中长时间卡在safepoint,尤其在高并发同步块密集调用时 - 实际影响:一次撤销平均耗时几十微秒,但 STW 时间取决于最慢的那个线程——可能达毫秒级,对延迟敏感服务(如金融、实时推荐)不可接受
- 注意:
synchronized(obj)触发撤销时,统计的是obj.getClass()的撤销次数,不是单个对象;所以批量重偏向/撤销都按 class 维度聚合
BiasedLockingBulkRebiasThreshold 和 BiasedLockingBulkRevokeThreshold 怎么影响行为?
这两个阈值只在偏向锁启用时生效(即 -XX:+UseBiasedLocking),控制 JVM 对某个 class 下对象的“抢救策略”:
-
BiasedLockingBulkRebiasThreshold = 20:同一 class 的对象发生 20 次撤销后,JVM 尝试批量重偏向——给 class 的 epoch 加 1,并把所有已偏向对象的线程 ID “悄悄”替换成当前竞争线程(不进 STW) -
BiasedLockingBulkRevokeThreshold = 40:达到 40 次后,直接批量撤销该 class 所有对象的偏向状态,后续新建对象也不再启用偏向锁 - 关键区别:
synchronized(obj.getClass())才真正进入这个 class 级统计;而synchronized(obj)只影响单个对象,但撤销仍需走 class 级决策路径
JDK 15+ 默认禁用后,synchronized 实际走哪条路径?
没有偏向锁,synchronized 的锁升级路径变成:无锁 → 轻量级锁(thin lock)→ 重量级锁。现代 JVM(JDK 17~21)重点优化的是轻量级锁的自适应自旋:
- 轻量级锁用 CAS 尝试获取,失败后不立即阻塞,而是根据历史竞争情况决定自旋次数(adaptive spinning)
- 自旋本身在用户态完成,避免内核切换;现代 CPU 的 CAS 和分支预测足够快,短临界区下比“省一次 CAS 却换来 STW”更划算
- 实测对比:在 8 核以上、平均锁持有时间
- 别误配:
-XX:+UseBiasedLocking在 JDK 21+ 启动会报 warning 并被忽略;JDK 20 起该参数已无效
真正容易被忽略的点是:偏向锁的消亡不是因为“它错了”,而是因为它的假设(单线程长期持有)和现代应用模式(短生命周期对象 + 多线程轮询访问)彻底错位。分析撤销代价,最终是为了看清——锁优化的重心早已从“省一次原子操作”转向“控住 STW、压平延迟毛刺”。

















