偏向锁撤销必然触发stw,因其需在全局安全点暂停所有线程以原子修改共享对象头和私有栈帧中的锁记录;jdk15+默认禁用,因现代多线程场景下撤销开销远超轻量级锁自旋收益。

偏向锁撤销不是“慢了一点”,而是会强制触发全局安全点(Safepoint)并导致 Stop-The-World(STW)——这是现代高并发 Java 应用中必须规避的硬伤,不是调参能救的。
为什么偏向锁撤销必然引发 STW
撤销操作不能在任意时刻执行,因为要同时修改:对象头的 Mark Word(所有线程共享)和线程栈帧里的 Monitor Record(每个线程私有)。JVM 必须等所有应用线程都到达一个安全点(比如方法返回、循环边界、字节码指令间隙),才能统一暂停它们完成状态修正。
这个等待过程本身就会卡住所有线程。哪怕只持续几毫秒,在 QPS 上万的服务里也可能造成请求毛刺或超时堆积。
常见错误现象包括:
- GC 日志里频繁出现
Safepoint相关耗时(如ThreadLocalHandshakes或no vm operation等待) - 应用响应时间突增,但 CPU 使用率未明显升高
-
jstack抓取的线程 dump 中大量线程处于VM Thread阻塞态
批量撤销阈值如何放大问题
JVM 不是逐个撤销,而是以 Class 为单位统计撤销次数。两个关键阈值直接决定是否波及整个类的所有对象:
-
BiasedLockingBulkRebiasThreshold = 20:某 class 下的对象发生 20 次撤销,触发批量重偏向(加 epoch、尝试悄悄换主) -
BiasedLockingBulkRevokeThreshold = 40:达到 40 次后,对该 class 所有已偏向对象批量撤销,后续新建对象也不再启用偏向锁
注意:synchronized(obj) 和 synchronized(obj.getClass()) 的行为完全不同。后者才真正落入这个统计体系;前者只是对单个对象加锁,不触发类级统计。
性能影响明显:一次批量撤销可能波及成百上千个同类型对象,而每个撤销都需走一遍 STW 流程。
JDK 15+ 默认禁用不是“过时”,而是模型失效
偏向锁的设计前提(单线程长期持有)在现代服务中早已不成立。真实场景中:
- 连接池、缓存 key 锁、HTTP 请求上下文对象,常被多个线程轮番访问
- 短生命周期对象(如每次请求 new 出来的 DTO)刚“认主”就被竞争,根本没机会享受“零成本加锁”
- 轻量级锁的
CAS + 自适应自旋在多核 CPU 上效率足够高,且无需 STW
-XX:-UseBiasedLocking 已成 Spring Boot 3.x、Quarkus 等主流框架默认推荐配置。真正容易被忽略的是它的“延迟启用”机制:BiasedLockingStartupDelay=4000(毫秒),会让它在应用刚启动时看似有效,但一旦流量涌入、竞争出现,撤销风暴就在所难免。
怎么验证你的应用是否受偏向锁拖累
不要只看 JVM 参数是否开启,重点观察运行时行为:
- 加参数
-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1,观察日志中RevokeBias类型 Safepoint 是否高频出现 - 用
jcmd <pid> VM.native_memory summary</pid>查看Internal区域内存增长是否异常(偏向锁撤销相关结构体分配) - 对比开启/关闭
-XX:-UseBiasedLocking后的 P99 响应时间和 GC STW 时间分布(用G1PeriodicGC或ZGC更易凸显差异)
现代多核下的同步优化重心,早已从“假设无竞争”转向“快速应对竞争”——轻量级锁、自适应自旋、锁消除这些机制不依赖线程行为假设,也完全避开 STW,才是真正可信赖的路径。










