JVM 不在字节码层面做乘除到移位的转换,该优化由 JIT 编译器(主要是 C2)在生成机器码时完成:乘以 2 的幂次(如 8)转为左移,31 等转为移位减法;除法仅在能静态证明操作数非负时才可能转为右移,否则保留 idiv;手写位运算无性能收益且易引发可读性、语义和溢出问题。

JVM 不会在字节码层面做乘除法到移位的转换,这类优化完全由运行时的 JIT 编译器(主要是 HotSpot 的 C2 编译器)在生成机器码阶段完成。你写的 int x = a * 8 或 int y = b / 4,javac 编译后仍是原始的 imul 和 idiv 字节码指令;真正“变快”的环节,发生在代码被频繁执行、触发 JIT 编译之后。
哪些乘除会被自动转成移位?
JIT 主要针对两类场景做强度削减(strength reduction):
-
乘以 2 的幂次:如
* 2、* 16、* 64,直接替换为、、 -
乘以形如 2ⁿ − 1 的常数:如
* 31→(x ,* 63→(x ,利用移位+减法替代单次乘法
为什么除法优化更谨慎?
整数除法语义是“向零取整”,而右移(>>)对负数是“向下取整”(带符号补高位),二者行为不一致:
-
-9 / 2得-4(数学除法) -
-9 >> 1得-5(符号右移)→ 结果错误 - JIT 只有在能**静态证明操作数非负**(例如数组索引计算、循环变量)时,才可能将
/ 2替换为>> 1;否则保留idiv
你不需要手动写位移,反而可能有害
手写 或 >> 替代乘除,既无性能收益,还引入三类风险:
-
可读性下降:
x 不如x * 8直观,尤其对新成员或跨语言开发者 -
语义陷阱:忘记括号导致优先级错误(
a + b ≠(a + b) ),或误用>>处理负数 -
溢出静默失败:
1 在 int 中得Integer.MIN_VALUE,不会报错,但结果已失真
怎么确认 JIT 真的优化了?
用 JMH 做微基准测试,并配合 JVM 参数观察:
- 加
-XX:+PrintAssembly(需 hsdis),查看生成的汇编是否含shl/shr指令而非imul/idiv - 加
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation,确认方法是否被 C2 编译 - 避免用简单循环测时间——现代 CPU 分支预测、流水线、缓存都会干扰结果











