jvm执行monitorenter时先检查mark word状态:若为无锁或偏向锁则尝试轻量级锁;仅当mark word指向他人lock record时才触发inflate()创建objectmonitor并升级为重量级锁。

monitorenter 指令触发时,JVM 怎么决定是否走重量级锁?
不是一执行 monitorenter 就立刻创建 ObjectMonitor。JVM 先检查对象头的 Mark Word 状态:如果当前是无锁或偏向锁(且未被撤销),它会尝试用 CAS 抢占轻量级锁;只有当发现已有线程在持有轻量级锁、而自己又不是持有者时,才判定为竞争发生,进入膨胀流程。
关键判断点就两个:
– Mark Word 是否指向某个线程栈中的 Lock Record(轻量级锁标识)
– 该 Lock Record 是否属于当前线程
不满足任一条件,monitorenter 就不再自旋或重试,而是调用 ObjectSynchronizer::inflate() 启动锁膨胀。
inflate() 过程中 ObjectMonitor 是怎么被创建和挂载的?
ObjectMonitor 是 C++ 层堆上分配的对象,不是 Java 对象,也不受 GC 管理。它的生命周期与锁对象绑定,由 JVM 在首次需要重量级语义时惰性创建。
- 调用
omAlloc(Thread::current())分配内存,初始化_owner、_EntryList、_WaitSet等字段 - 用原子操作将对象头的
Mark Word替换为指向该ObjectMonitor的指针,同时把锁状态位设为10(重量级锁) - 原轻量级锁持有者的线程 ID 被写入
ObjectMonitor._owner;若该线程已退出同步块,则_owner = null,新线程可直接获取
注意:这个替换必须是原子的,否则会出现多个线程看到不同状态的 Mark Word,导致状态混乱。
为什么 monitorexit 不会触发锁降级?
monitorexit 只负责释放锁、唤醒 _EntryList 中的一个线程,并清空 ObjectMonitor._owner。它不会检查当前是否还有竞争、也不会尝试把 ObjectMonitor 回收或还原成轻量级锁结构。
原因很实际:
– 锁对象头已经指向了 ObjectMonitor,降级需改写 Mark Word 并回收 native 内存,开销不比维持现状小
– JVM 认为一旦升级过,后续大概率还会竞争,继续走重量级路径更稳
– 没有机制跟踪“最近 N 次是否真有竞争”,无法安全判断是否该降级
所以重量级锁是单向的:膨胀后,只要该对象还在被 synchronized 使用,就一直保持重量级状态。
调试时怎么确认 monitorenter 走的是重量级路径?
最直接的方式是结合字节码 + 对象头 + 日志:
– 用 javap -v 确认存在 monitorenter 指令
– 用 JOL(ClassLayout.parseInstance(obj).toPrintable())打印对象头,观察 Mark Word 是否为类似 0x0000000000000000(全 0 表示未初始化)或已含有效指针
– 加 JVM 参数 -XX:+PrintSynchronizationStatistics -XX:SyncKnobs=print_inflation,启动时能看到每把锁的膨胀日志,例如:Inflating object 0x00000000c0001234 to monitor 0x00007f8a1c004560
别依赖 Thread.getState() 判断——线程处于 BLOCKED 状态只说明它在等锁,但无法区分是等轻量级锁自旋失败,还是等重量级锁的 _EntryList 唤醒;真正可靠的信号永远是对象头内容和膨胀日志。











