锁消除是jvm在jit编译阶段通过逃逸分析发现对象未逃逸时,自动省略加锁/解锁指令的优化;字节码保留synchronized,但机器码中无实际锁操作,无需程序员手动删除。

锁消除不是程序员删掉 synchronized,而是 JVM 在 JIT 编译阶段,通过逃逸分析发现某个对象只在当前线程内使用、不会被其他线程访问,于是直接跳过生成加锁/解锁指令——字节码里还写着锁,但机器码里根本没它。
逃逸分析是锁消除的前提
JVM 先判断一个对象会不会“逃出”当前方法或线程。判定依据很具体:
- 对象是否作为返回值返回
- 是否赋值给静态变量或实例字段
- 是否传入可能跨线程调用的方法(比如
executor.submit()、new Thread().start()) - 是否通过反射、Lambda 捕获、varargs 数组等方式间接暴露
只要其中任意一条成立,对象就被标记为“逃逸”,锁就无法消除。只有标为 NoEscape 的对象,才进入锁消除候选队列。
哪些代码容易触发锁消除
典型场景都满足“局部创建 + 单线程使用 + 不共享”三个条件:
-
StringBuffer sb = new StringBuffer(); sb.append("a").append("b");—— 虽然append是同步方法,但sb没逃逸,锁被抹掉 -
Vector<string> list = new Vector(); list.add("x"); list.size();</string>—— 仅在方法内临时用,不返回也不传参 - 自定义的
synchronized方法对象:Counter c = new Counter(); c.increment();,且c生命周期完全局限在当前栈帧
注意:哪怕用了 ReentrantLock.lock(),只要锁对象未逃逸,JIT 同样可能优化掉整个同步逻辑。
锁消除不是总生效,得看运行时条件
逃逸分析只是门槛,不是通行证。真正触发锁消除还需满足:
- 方法已被 JIT 认定为热点(调用次数达标,进入 C2 编译层级)
- 未禁用相关 JVM 参数:
-XX:+DoEscapeAnalysis(JDK8+ 默认开)、-XX:+EliminateLocks - 没踩干扰陷阱:比如调用
System.out.println()、Thread.currentThread()、任意 native 方法,或存在复杂控制流 - 不在调试模式(
-Xdebug)或刚启动的冷启动阶段(JIT 尚未介入)
可以用 -XX:+PrintEscapeAnalysis -XX:+PrintCompilation 查看日志,出现 lock elided 或 eliminated synchronized 就说明成功了。
锁消除的本质是编译期静默优化
它不改源码、不改字节码语义,只在 JIT 把字节码翻译成机器码那一瞬间做决策:
- 字节码中仍有
monitorenter/monitorexit指令 - JIT 编译器选择不把它们翻译成实际的锁操作
- 没有内存屏障、没有上下文切换、没有竞争等待——相当于这段同步逻辑“不存在”
这种优化对开发者透明,但理解它能帮你写出更易被优化的代码:少让局部对象“意外逃逸”,就是给 JVM 省事,也给自己省性能。










