monitorenter和monitorexit是jvm底层字节码指令,分别负责获取和释放对象锁,通过对象头mark word与objectmonitor协作实现可重入、异常安全的同步机制。

monitorenter 和 monitorexit 到底在干啥
它们不是 Java 代码里的函数,而是 JVM 执行同步块时插入的底层字节码指令——monitorenter 负责“抢锁”,monitorexit 负责“放锁”。你写 synchronized(obj) { ... },编译器就自动给你包上这一对指令;它不关心 obj 是不是 null、是不是 final,只认这个引用值。
关键点在于:锁的归属和计数全靠对象头里的 mark word 和关联的 ObjectMonitor 结构维护,而这两条指令就是触发这套机制的开关。
-
monitorenter执行时,JVM 检查目标对象是否已被当前线程持有;如果是,计数器 +1(可重入);如果不是,尝试获取锁,失败则进EntryList阻塞 -
monitorexit每执行一次,计数器 -1;只有计数器归零时,锁才真正释放,其他线程才可能被唤醒 - 哪怕同步块里抛了未捕获异常,JVM 也保证至少有一个
monitorexit执行——这是通过字节码里的Exception table实现的,不是 try-finally 编译出来的
为什么 javap 看到两个 monitorexit?
因为安全。一个给正常流程用,一个专为异常兜底——你看 javap -v 输出,总能看到类似这样的结构:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2
7: ldc #3
9: invokevirtual #4
12: aload_1
13: monitorexit <-- 正常出口
14: goto 22
17: astore_2
18: aload_1
19: monitorexit <-- 异常出口
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
没这个双保险,一旦同步块里 NPE 或 OOM,锁就永远卡住——这比性能问题更致命。
- 别自己手写
monitorenter/monitorexit:它们是 JVM 内部指令,不能直接调用,也不在 Java API 里 - 不要以为加了
try-catch就能替代这个机制:JVM 的异常表处理是字节码层的,比 Java 层的异常处理更早、更底层 - 如果反编译发现只有一个
monitorexit,那基本说明代码路径极简(比如空块),或编译器做了激进优化(少见,JDK 8+ 后基本都保留双出口)
ACC_SYNCHRONIZED 和 monitorenter 的区别在哪
根本不是“两种实现”,而是同一套机制的两种字节码表达形式:ACC_SYNCHRONIZED 是方法级的“快捷方式”,monitorenter/monitorexit 是代码块级的“手动档”。
比如 public synchronized void m() { } 编译后不会出现那两条指令,而是在方法属性里打上 ACC_SYNCHRONIZED 标志;JVM 运行时看到这个标志,会自动在方法入口插 monitorenter,出口插 monitorexit(同样带异常兜底)。
- 静态同步方法锁的是
Class对象,实例方法锁的是this,但底层都是对某个对象执行monitorenter -
ACC_SYNCHRONIZED方法无法指定锁对象,灵活性不如代码块;但少了显式对象引用,字节码更紧凑 - 二者锁升级路径完全一致(偏向锁 → 轻量级锁 → 重量级锁),因为最终都落到同一个
ObjectMonitor实例上
容易被忽略的坑:null、锁对象生命周期、锁粒度
monitorenter 遇到 null 会直接抛 NullPointerException,而且是在字节码执行阶段——不是进入方法后才检查。这意味着,如果你传了个可能为 null 的锁对象,NPE 会发生在同步块第一行之前,且无法被该块内的 catch 捕获。
- 永远不要用可能为 null 的变量做锁对象,比如
synchronized(map.get("key"))—— 万一 key 不存在,map.get 返回 null,直接炸 - 锁对象要是长期稳定存在的;比如用局部 new 出来的
new Object()当锁,每次调用都新建,等于没锁 - 锁粒度要匹配场景:锁
this可能导致整个对象被串行化;锁内部私有 final 字段(如private final Object lock = new Object())才是常见安全做法 - monitor 的重量级状态(即真正调用 OS mutex)一旦触发,线程挂起/唤醒开销极大;高频短临界区反而比无锁原子操作慢得多
Monitor 不是黑盒,它是 C++ 实现的、和操作系统线程强绑定的结构;看懂 monitorenter 和 monitorexit 的行为,等于摸到了 synchronized 的脉门——其它所有优化(锁消除、锁粗化、自旋)都是在这个基础上叠的。










