锁升级机制以mark word变化为核心,按竞争场景单向不可逆升级:无锁→偏向→轻量→重量,每步解决特定问题并伴随相应代价,需结合jol、jvm参数及实验验证。

直接从对象头和竞争场景切入最有效。别一上来就背“无锁→偏向→轻量→重量”这个链条,得先明白每一步背后解决的是什么问题、代价是什么、JVM怎么感知到该升级了。
盯住 Mark Word 的 64 位变化
所有升级动作都写在对象头的 Mark Word 里。它不是固定存一个东西,而是复用同一块内存,按锁状态切换含义:
- 无锁时:存哈希码、分代年龄,锁标志位是 01
- 偏向锁时:存线程 ID + epoch,偏向位为 1,锁标志还是 01
- 轻量级锁时:存指向栈中 Lock Record 的指针,锁标志变成 00
- 重量级锁时:存指向堆中 Monitor 对象的指针,锁标志变成 10
用 javap -v 看字节码只能看到 monitorenter/monitorexit,真正要验证锁状态,得用 jol(Java Object Layout)工具打印对象内存布局,或者用 -XX:+PrintBiasedLockingStatistics 开关观察运行时统计。
按真实竞争节奏理解升级触发点
升级不是定时发生的,而是被线程行为“逼出来”的:
- 偏向锁启动:第一个线程进同步块,JVM 尝试用 CAS 把自己 ID 写进 Mark Word;成功就进入偏向状态
- 偏向锁撤销:第二个线程来抢锁,发现已有偏向,就得等全局安全点(Stop-The-World),然后把偏向锁“撤掉”,转成轻量级锁
- 轻量级锁自旋:多个线程交替抢锁,但不同时撞上——JVM 让后来者自旋(空循环)几十次,赌前一个线程快释放;失败就升级
- 升级重量级锁:自旋耗尽,或已有线程在等待队列里,JVM 就把锁膨胀,Mark Word 指向 Monitor,线程进 OS 队列阻塞
动手跑几个对比实验
光看理论容易飘,建议写三段最小可验证代码:
- 单线程反复调用 synchronized 方法 → 观察是否停留在偏向锁(JDK 15+ 默认关闭,需加
-XX:+UseBiasedLocking) - 两个线程交替执行同步块 → 看轻量级锁是否生效,
jstack中线程状态应为RUNNABLE而非BLOCKED - 十个线程同时争一个锁 → 很快看到
jstack输出大量BLOCKED,对应 Monitor 的 Owner 和 EntryList
配合 JVM 参数如 -XX:+TraceClassLoading、-XX:+PrintGCDetails(GC 会触发安全点,影响偏向锁撤销),能看清升级发生的实际时机。
避开常见认知陷阱
很多资料讲得模糊,这几个点必须厘清:
- 锁升级是单向不可逆的:一旦变重量级,整个生命周期不会再降回轻量或偏向
- 偏向锁不是“性能更好”,而是“单线程场景下开销更低”;多线程环境下,它的撤销成本可能比直接上轻量锁还高
- Monitor 不是 Java 类,是 JVM 用 C++ 实现的底层结构,包含 Owner、EntryList、WaitSet 三个关键字段,和操作系统互斥量绑定
- 所谓“锁粗化”“锁消除”是 JIT 编译器做的优化,和锁升级机制并行存在,但属于不同层级
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











