锁膨胀是JVM自适应竞争的优化机制,分偏向锁(零开销但单线程)、轻量级锁(自旋换切换)、重量级锁(阻塞不可逆)三阶段,各阶段性能代价递增,需匹配业务场景。

锁膨胀本身不是问题,而是JVM对竞争变化的自适应响应。它直接影响的是同步代码段的执行开销和线程调度行为——越往后的锁状态,性能代价越高。
偏向锁阶段:几乎零开销,但只适用于单线程场景
对象刚被创建、首次被某个线程用 synchronized 访问时,JVM会把它标记为偏向锁,并记录该线程ID。此后同一线程再次进入同步块,无需CAS或系统调用,直接通过比对线程ID就能通行。
- 适用典型场景:单线程反复调用的工具类方法(如日志格式化、本地缓存读取)
- 撤销成本明显:一旦有第二个线程尝试获取该锁,JVM必须暂停所有相关线程做偏向撤销,这个过程会触发全局安全点(safepoint),带来短暂STW
- 批量重偏向阈值是20次撤销,超限后会对同类对象统一更新Epoch;再超40次就禁用该类的偏向锁
轻量级锁阶段:用CPU自旋换线程切换,适合短时错峰竞争
偏向锁被撤销后,若线程仍尝试加锁,JVM会在当前线程栈中创建“锁记录(Lock Record)”,并用CAS把对象头的Mark Word替换为指向该记录的指针。
- 成功则获得锁,后续重入只需在栈中新增一条记录
- 失败说明已有其他线程持有轻量级锁,当前线程开始自旋(默认10次,可由
-XX:PreBlockSpin调整) - 自旋期间不释放CPU,但避免了上下文切换;若自旋失败次数过多,或检测到锁被长时间占用,就触发膨胀
重量级锁阶段:真正阻塞,开销最大但最稳
一旦升级为重量级锁,对象头Mark Word将存入指向ObjectMonitor的指针,所有争抢线程都会被挂起,进入操作系统级等待队列(EntryList 或 cxq)。
立即学习“Java免费学习笔记(深入)”;
- 线程从RUNNABLE → BLOCKED,涉及用户态到内核态切换,一次切换耗时通常在几百纳秒到几微秒
- 唤醒依赖OS调度,存在不可控延迟;如果持有锁的线程发生GC停顿、缺页、IO等待等,等待线程只能干等
- 锁膨胀至此不可逆——即使之后竞争消失,也不会降级回轻量级或偏向锁
锁膨胀对整体性能的真实影响
不是“膨胀了就变慢”,而是不同阶段放大了不同瓶颈:
- 偏向锁撤销频繁 → 频繁进入safepoint → 应用出现规律性卡顿
- 轻量级锁自旋过多 → CPU空转率升高 → 单核高负载、多核利用率不均
- 重量级锁大量存在 → 线程频繁阻塞/唤醒 → 上下文切换飙升、平均延迟跳升、吞吐下降
- 监控指标可关注:
java.lang:type=Threading中的ThreadContentionMonitoringEnabled和PeakThreadCount,配合 GC 日志与jstack分析锁等待链
基本上就这些。锁膨胀机制本身是JVM的优化策略,关键不在避免膨胀,而在理解你的业务锁模式是否匹配对应阶段——比如高并发短临界区适合轻量级锁,而长事务型操作早该考虑无锁结构或分段锁。











