jvm锁消除需满足逃逸分析判定对象不逃逸,即局部变量未返回、未传参、未存入静态字段或数组;锁粗化仅对高频连续同对象同步操作有效,且依赖jit编译优化生效。

怎么确认 JVM 真的做了锁消除
锁消除不是代码写出来就生效的,它依赖 JIT 编译器在运行时做逃逸分析。如果 StringBuffer 是方法内新建的局部变量、没被返回、没传给其他方法、也没被写入静态字段或数组,JVM 才可能判定它“不逃逸”,进而消除 append() 上的 synchronized。
常见误判点:
- 把
sb作为返回值(比如return sb;),哪怕只返回一次,逃逸分析大概率失败 - 调用
sb.toString()后又把结果存进static字段或全局容器里,也会导致逃逸 - 用
-XX:+PrintEscapeAnalysis可以看到逃逸分析日志,但需配合-XX:+UnlockDiagnosticVMOptions;更实用的是用 JMH +-prof perfasm观察热点指令是否还包含 monitorenter/monitorexit
锁粗化在什么场景下真能提速
锁粗化不是“把锁范围拉大”就一定快,它只对高频、连续、同对象的加锁/解锁序列有效。典型触发条件是:循环体内反复调用同一个对象的同步方法,且循环次数足够多(通常 >10 次),JIT 才会把整个循环包进一个同步块。
注意边界情况:
- 循环体里混有非同步操作(比如中间穿插了 I/O 或 sleep),JIT 很可能放弃粗化
- 同步对象在循环中被重新赋值(如
sb = new StringBuffer()),粗化失效 - 使用
-XX:+PrintCompilation能看到类似123 b java.lang.StringBuffer::append (35 bytes)的日志,若后续编译版本号上升且字节数变小,可能是粗化后内联或简化了同步逻辑
为什么开了优化反而性能没变化甚至下降
锁消除和粗化都是 JIT 阶段的激进优化,它们生效的前提是:代码已稳定执行足够多次(默认 10000 次热点阈值),触发 C2 编译。冷启动阶段、短生命周期应用(如 CLI 工具)、或压测时间太短,都看不到效果。
容易被忽略的干扰项:
- JVM 默认开启偏向锁(
-XX:+UseBiasedLocking),而偏向锁在多线程争抢初期反而有额外开销;高并发下可尝试关掉它对比 baseline - 锁粗化合并后,虽然减少了 monitorenter/monitorexit 指令数,但如果粗化后的临界区变长,反而加剧了线程排队——这在高争用场景下可能比原来更慢
- 用
StringBuffer做拼接本身已是过时实践;即使锁被消除,其内部char[]扩容仍比StringBuilder多一次数组拷贝,别拿它当性能标杆
实操验证建议:别只看吞吐量,盯住指令级差异
真正判断优化是否落地,不能只比 System.nanoTime(),得看 JIT 编译后的机器码。最直接的方式是:
- 用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=print,*YourClass.yourMethod获取汇编输出,搜lock前缀或cmpxchg指令出现频次 - 用
jstack -l抓线程栈,观察Object.wait()或Unsafe.park()是否还在频繁出现——如果没了,说明锁已被消除或升级路径绕过了重量级锁 - 对同一段逻辑,分别用
-XX:-EliminateLocks和-XX:-DoEscapeAnalysis关闭优化再跑对比,差值才是真实收益
锁消除和粗化不是银弹,它们只在特定模式下悄悄起效。一旦代码结构稍有变动(比如加个日志、改个参数传递方式),优化就可能失效。与其纠结“为什么没提升”,不如先确认:这段代码是不是真的被 JIT 编译了?逃逸分析结论到底是什么?










