锁消除是jit编译器在生成机器码时跳过对未逃逸对象的加锁/解锁指令,依赖逃逸分析确认对象仅被单线程使用,从而省略monitorenter和monitorexit,而非删除synchronized关键字。

锁消除不是删掉 synchronized 关键字,而是 JIT 编译器在生成机器码时,跳过对未逃逸对象的加锁/解锁操作。它依赖逃逸分析判断对象是否只被当前线程使用,一旦确认无多线程竞争可能,就直接省掉 monitorenter 和 monitorexit 指令。
逃逸分析怎么判断对象“没跑出去”
JVM 分析对象的创建和使用范围,只要满足全部以下条件,就判定为“未逃逸”:
- 不作为方法返回值(比如没写
return sb;) - 不赋值给静态变量或全局容器(如
static Map、缓存池) - 不传入可能跨线程执行的方法(如
executor.submit()、Thread.start()) - 不被写入其他已逃逸对象的字段中(比如把局部 StringBuffer 存进一个 static List 的元素里)
哪些代码容易触发锁消除
典型场景是方法内临时构造的线程安全类,它们的同步逻辑在单线程上下文中纯属冗余:
-
StringBuffer sb = new StringBuffer(); sb.append("a").append("b");—— append 是 synchronized,但 sb 未逃逸,锁被跳过 -
Vector<string> list = new Vector(); list.add("x");</string>—— 局部使用、不共享,同步开销被抹除 -
Counter c = new Counter(); c.increment();—— 自定义 synchronized 方法,只要 c 是方法内新建且未暴露,同样适用
锁消除生效的前提条件
它不是编译期改动,而是在运行时由 C2 编译器完成的本地代码优化,需要同时满足:
- 启用逃逸分析(Server 模式下默认开启,参数
-XX:+DoEscapeAnalysis) - 使用 C2 编译器(而非 C1),可通过
-XX:+PrintCompilation确认热点方法是否被 C2 编译 - 代码执行足够多次(默认阈值约 10000 次),触发 C2 编译后才起效
- 对象生命周期严格封闭在单一线程内,哪怕一次返回或一次存入静态结构,都会导致逃逸分析失败
怎么确认锁消除真的发生了
不能靠反编译看字节码(因为字节码里仍有 monitorenter),实用验证方式有:
- 加 JVM 参数
-XX:+PrintEscapeAnalysis -XX:+PrintCompilation,观察日志中是否出现eliminated或lock elided - 用 JMH +
-prof perfasm查看热点方法汇编,确认monitorenter指令是否消失 - 对比开启/关闭逃逸分析的性能差异:加
-XX:-DoEscapeAnalysis后,StringBuffer.append耗时通常上升约 15%
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











