锁消除是jit运行时动态移除无竞争同步的操作,前提为逃逸分析确认对象未逃逸;仅适用于内置锁、局部对象等场景,不支持this、reentrantlock、数组及反射访问对象。

锁消除不是删代码,是 JIT 的自动判断
锁消除不是程序员手动去掉 synchronized,也不是编译器在编译期(如 javac)就抹掉字节码。它是 HotSpot JVM 的 JIT 编译器(主要是 C2 编译器)在运行时对热点代码做深度分析后,动态移除“根本不会发生竞争”的同步操作。前提是:对象没逃逸、锁无实际保护意义。
逃逸分析是锁消除的唯一前提
JVM 必须先确认一个对象的生命周期完全局限在当前线程内,才敢删它的锁。判定标准很具体:
- 对象在方法内 new 出来,没作为返回值传出
- 没赋值给静态字段、堆中对象的成员变量或全局容器(如 static List)
- 没被传入可能跨线程的方法(比如 Thread.start()、Executor.submit()、日志框架的 log.info())
- 没被 synchronized 块以外的代码持有引用(例如传给某个工具方法后又被存起来)
只要其中一条不满足,逃逸分析就会标记该对象“已逃逸”,锁消除立刻失效。
典型可消除场景与常见误区
以下代码中的锁大概率会被消除:
- 局部 StringBuffer/StringBuilder 使用:方法内新建,仅调用 append/toString,不暴露引用
-
方法内新建锁对象:
Object lock = new Object(); synchronized(lock) { ... } - 私有 final 字段上的同步块(若该字段本身未逃逸且所属对象也未逃逸)
但这些情况通常无法消除:
- synchronized(this):this 几乎总逃逸(比如被返回、加入集合、作为监听器注册)
- ReentrantLock 实例:JIT 只识别内置锁(monitor),不处理 AQS 类锁
- 数组对象:逃逸分析不支持数组,即使局部创建也无法锁消除
- 被反射或 Unsafe 操作访问的对象:分析器无法保证语义安全,直接放弃优化
验证锁消除是否生效的方法
不能只看 JVM 参数是否开启,得观察实际行为:
- 加参数 -XX:+PrintEscapeAnalysis -XX:+UnlockDiagnosticVMOptions,启动时输出逃逸判定日志
- 用 -XX:+PrintCompilation 确认目标方法是否被 C2 编译(只有 C2 阶段才做锁消除)
- 对比开启/关闭逃逸分析的性能差异:-XX:+DoEscapeAnalysis vs -XX:-DoEscapeAnalysis,用 JMH 测 StringBuffer.append 性能,差距明显说明优化起了作用
- 检查 GC 日志:若某段代码分配对象速率大幅下降,可能是栈上分配+锁消除共同生效










