锁消除是JIT编译器通过逃逸分析识别线程私有对象后,自动移除其synchronized锁的优化技术;它不改变语义,仅在C2编译后的热点代码中跳过monitorenter/monitorexit指令,典型如局部StringBuilder的内部锁被彻底消除。

Java 中的锁消除(Lock Elision)是 JIT 编译器在运行时通过逃逸分析(Escape Analysis)识别出“不会被其他线程访问”的对象,从而自动移除其上不必要的 synchronized 锁操作。它不是语法层面的删除,而是编译优化后的字节码/机器码中彻底不执行加锁逻辑。
逃逸分析是锁消除的前提
逃逸分析判断一个对象的引用是否“逃逸”出当前方法或线程的作用域:
- 不逃逸:对象仅在当前方法内创建、使用,且引用未传递给其他方法、未存储到堆中(如静态变量、其他对象字段)、未作为返回值传出;
- 逃逸:对象引用被发布到方法外(例如赋值给全局变量、放入集合、作为参数传入其他方法等),此时可能被多线程访问,JIT 不敢删锁。
只有当同步块内的对象被判定为“线程私有、无共享风险”时,JIT 才会触发锁消除。
典型可消除锁的场景
以下代码在开启逃逸分析(默认开启,-XX:+DoEscapeAnalysis)和锁消除(-XX:+EliminateLocks,JDK 6u23+ 默认启用)后,synchronized 块会被优化掉:
立即学习“Java免费学习笔记(深入)”;
// 示例:局部 StringBuilder 在方法内使用,无逃逸
public String concat(String a, String b) {
StringBuilder sb = new StringBuilder(); // 在栈上分配(标量替换后),不逃逸
synchronized (sb) { // JIT 发现 sb 不逃逸 → 锁消除
sb.append(a).append(b);
}
return sb.toString();
}
关键点:
- sb 是方法内新建的局部对象;
- 未将 sb.this 或其引用暴露给外部;
- 即使加了 synchronized,实际执行时不会调用 monitorenter/monitorexit 指令。
为什么不能靠人工判断?JIT 的优势在哪
人很难静态确认“绝对线程安全”,但 JIT 在运行时结合多种信息做更精准判断:
- 跟踪对象的实际分配路径与引用传播(如是否进入 HashMap、是否被反射访问);
- 结合分层编译,在 C2 编译阶段做深度逃逸分析(比 C1 更激进);
- 支持标量替换(Scalar Replacement):若对象不逃逸,可将其字段拆解为局部变量,进一步避免锁和堆分配。
例如,Vector 内部加锁,但若你只在单线程方法里创建并使用它,JIT 同样可能消除其内部所有 synchronized 调用。
验证锁是否被消除
可通过 JVM 参数观察优化效果:
- -XX:+PrintCompilation:查看方法是否被 C2 编译;
- -XX:+UnlockDiagnosticVMOptions -XX:+PrintEscapeAnalysis:打印逃逸分析结果(如 “sb: allocated in concat(), not escaped”);
- -XX:+PrintOptoAssembly(需 hsdis):查看生成的汇编,确认 monitorenter 是否消失。
注意:锁消除只发生在 JIT 编译后的热点代码,解释执行阶段仍会正常加锁。


















