偏向锁撤销会触发STW,因其必须在全局安全点暂停所有线程以确保对象头修改和栈帧遍历的一致性;批量撤销还按类统计阈值(20次重偏向、40次全撤销),JDK15+默认禁用。

偏向锁撤销不是“慢了一点”,而是会触发全局安全点(Safepoint)并导致 Stop-The-World(STW),这是现代高并发 Java 应用中必须规避的硬伤。
为什么偏向锁撤销会引发 STW
撤销操作不能在任意时刻执行,因为要修改所有线程共享的锁对象头(Mark Word)和线程私有的 Monitor Record。JVM 必须等所有应用线程都到达一个安全点(Safepoint)——比如方法返回、循环边界、字节码指令间歇点——才能统一暂停它们进行状态修正。
- 这个等待过程本身就会卡住所有线程,哪怕只持续几毫秒,在 QPS 上万的服务里也可能造成毛刺或超时堆积
- 撤销后锁通常直接升级为重量级锁(涉及操作系统 mutex),而非轻量级锁,进一步放大开销
- 即使只是单个对象被撤销,只要它属于某个高频创建的 class,就可能触发批量撤销机制,波及成百上千个同类型对象
批量重偏向与批量撤销的阈值逻辑
JVM 不是逐个处理撤销,而是以 Class 为单位统计撤销次数,靠两个关键阈值做决策:
-
BiasedLockingBulkRebiasThreshold = 20:当某 class 下的对象发生 20 次撤销,JVM 认为“这把锁不该再偏向旧线程了”,触发批量重偏向——把 class 的epoch加 1,并尝试将所有已偏向该 class 的对象“悄悄换主”,避免下一轮再撤销 -
BiasedLockingBulkRevokeThreshold = 40:达到 40 次后,JVM 放弃抢救,对该 class 下所有对象执行批量撤销,后续新建对象也不再启用偏向锁
注意:这两个阈值只对同一 class 的对象累计,和具体锁对象无关;synchronized(obj) 和 synchronized(obj.getClass()) 的行为完全不同,后者才真正落入这个统计体系。
JDK 15+ 默认禁用偏向锁的真实原因
不是“优化过时”,而是收益/代价比彻底反转:
- 早期单核 CPU + 单线程任务多,偏向锁能省掉大量 CAS,收益明显
- 现在服务普遍跑在多核上,且同步块常被多个线程轮番访问(如连接池、缓存 key 锁),锁还没“偏”几次就被迫撤销
- 一次撤销的 STW 成本,远高于几十次轻量级锁的自旋开销;而轻量级锁的 CAS + 自适应自旋,在现代 CPU 上效率足够高
-
-XX:-UseBiasedLocking已成主流框架(Spring Boot 3.x、Quarkus)默认推荐配置
真正容易被忽略的是:偏向锁的“延迟启用”机制(BiasedLockingStartupDelay=4000)会让它在应用刚启动时看似有效,但一旦流量涌入、竞争出现,撤销风暴就在所难免——这不是调参能救的,是模型层面的不匹配。

















