JVM 剖析花园:1 - 锁粗化及循环
内容提要
这篇文章是JVM研发专家Aleksey Shipilëv撰写的系列文章之一,介绍了JVM的基本知识。文章讨论了Hotspot是否会对循环进行锁粗化优化,并通过实验验证了这一问题。实验结果显示,禁用循环展开可以提高性能,但不同的循环展开参数并没有明显的差异。文章总结了锁粗化对循环的优化方法,以及其带来的性能优势和范围限制。
延伸解读
锁粗化与循环展开的协作机制
文章通过反汇编分析揭示,Hotspot 并不会直接将整个循环内的锁外提,而是借助循环展开优化,将循环体复制多份,形成多个相邻的锁-解锁序列,再触发常规锁粗化。这种间接方式既减少了锁定开销,又避免了对大循环过度粗化导致线程长时间占锁。理解这一协作机制,有助于解释为何禁用循环展开后性能显著下降。
实验数据中的性能差异与参数影响
基准测试显示,默认设置下每次操作约 5331 纳秒,而使用 -XX:LoopUnrollLimit=1 禁用展开后升至约 20679 纳秒,性能相差约 4 倍。但译者补充的测试中,不同展开参数(1、2、4、8、16)的结果均在 18000 纳秒左右,并无明显倍差。这表明展开参数的具体数值可能不如“是否允许展开”关键,也提示读者需结合自身环境验证。
基准测试中的关键技巧与注意事项
作者在 JMH 基准中使用了几个重要技巧:通过 -XX:-UseBiasedLocking 禁用偏向锁以避免长达 5 秒的预热延迟;用 @CompilerControl(DONT_INLINE) 防止方法内联以便反汇编分析;并加入魔数 0x42 便于在指令中定位增量操作。这些细节提醒我们,JVM 性能实验需严格控制编译与运行时参数,否则观测结果可能失真。
Q&A
Hotspot 会不会把循环内的锁粗化到循环外面?
不会。Hotspot 不会将循环内的锁粗化到循环外部,但循环展开优化会为锁粗化创造条件,使得在展开后的代码中,相邻的锁块被合并。
为什么循环展开能提升锁粗化的性能?
循环展开将循环体复制多份,产生多个相邻的锁-解锁序列,从而触发锁粗化,将多个锁合并为一个,减少锁开销,提升性能。
禁用循环展开后性能下降多少?
在基准测试中,禁用循环展开(-XX:LoopUnrollLimit=1)后,性能从约 5331 ns/op 下降到约 20679 ns/op,性能下降约 4 倍。
实验中为什么要禁用偏向锁?
禁用偏向锁(-XX:-UseBiasedLocking)可以避免更长的预热时间,因为偏向锁不会立即启动,而是有约 5 秒的延迟。
锁粗化优化有什么潜在缺点?
锁粗化可能使锁变得过于粗糙,导致特定线程在执行大循环时长时间占用锁,影响其他线程。
不同的循环展开参数对性能有影响吗?
根据文章中的测试,不同的循环展开参数(如 -XX:LoopUnrollLimit=2,4,8,16)并没有带来明显的性能差异,性能与禁用展开时相近。