访问基本类型时MethodHandle性能不及Reflection的原因探究
问题
我希望以最高性能的方式通过反射调用一个返回基本类型long的方法。我分别使用Reflection和MethodHandle实现了该功能,原本预期MethodHandle更快——因为这是它的核心优势之一,且能避免Reflection存在的装箱/拆箱操作。但在所有JMH基准测试中,MethodHandle反而慢了约2%-5%。
测试用的JMH代码如下:
import org.openjdk.jmh.annotations.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) @State(Scope.Benchmark) public class AccessorBenchmark { private POJO source; private ReflectionAccessor reflectionAccessor; private MethodHandleAccessor methodHandleAccessor; @Setup public void setup() throws ReflectiveOperationException { source = new POJO(); final Method method = source.getClass().getDeclaredMethod("longMethod"); reflectionAccessor = new ReflectionAccessor(method); methodHandleAccessor = new MethodHandleAccessor(method); } @Benchmark public long reflectionAccessor() throws ReflectiveOperationException { return reflectionAccessor.get(source); } @Benchmark public long methodHandleAccessor() throws Throwable { return methodHandleAccessor.get(source); } public class ReflectionAccessor { private final Object[] EMPTY_ARGS = new Object[0]; private final Method method; public ReflectionAccessor(final Method method) { this.method = method; } public long get(final Object source) throws ReflectiveOperationException { return ((Number) method.invoke(source, EMPTY_ARGS)).longValue(); } } public class MethodHandleAccessor { private MethodHandle methodHandle; public MethodHandleAccessor(final Method method) throws ReflectiveOperationException { methodHandle = MethodHandles.lookup().unreflect(method); methodHandle = methodHandle .asType(methodHandle.type().changeReturnType(long.class).changeParameterType(0, Object.class)); } public long get(final Object source) throws Throwable { return (long) methodHandle.invokeExact(source); } } public class POJO { // Chose a value outside of the autoboxing cache range private final AtomicLong counter = new AtomicLong(Long.MAX_VALUE); /** Some dummy method that returns different values in consistent amounts of time */ public long longMethod() { return counter.addAndGet(-1); } } }
在Java 17环境下得到如下测试结果:
Benchmark Mode Cnt Score Error Units AccessorBenchmark.methodHandleAccessor avgt 25 4.204 ± 0.546 ns/op AccessorBenchmark.reflectionAccessor avgt 25 4.123 ± 0.040 ns/op
请问这是什么原因?
分析与解答
1. 反射的JIT优化抵消了装箱开销
Java 17对Method.invoke的装箱/拆箱操作做了深度优化。你的反射调用中,invoke返回的Long对象被强转为Number再调用longValue(),这个过程在JIT编译后会被完全消除,不会产生实际的装箱/拆箱成本,直接等价于获取原始long值。
2. MethodHandle的冗余类型转换引入额外开销
你在创建MethodHandle时执行了不必要的asType操作:
- 原方法
longMethod的返回类型已经是long,changeReturnType(long.class)属于重复操作; - 将参数类型从
POJO改为Object,导致MethodHandle在调用时需要额外执行类型检查与适配,而原生MethodHandle可以直接匹配POJO类型参数,不需要这层转换。
这部分额外的类型适配逻辑,就是MethodHandle性能略低的关键原因。
3. 反射调用路径被JIT深度内联
现代JVM对高频反射调用的优化已经非常成熟:当一个Method实例被频繁调用时,JIT会生成直接调用目标方法的机器码,性能几乎和直接调用一致。而带有类型适配的MethodHandle,其调用逻辑无法被完全内联,反而保留了额外的处理步骤。
修正方案
去掉MethodHandle的冗余类型转换,直接匹配原始方法的参数类型:
public class MethodHandleAccessor { private MethodHandle methodHandle; public MethodHandleAccessor(final Method method) throws ReflectiveOperationException { // 去掉多余的asType转换,直接使用原生MethodHandle methodHandle = MethodHandles.lookup().unreflect(method); } public long get(final POJO source) throws Throwable { // 直接传入POJO类型实例,匹配invokeExact的类型要求 return (long) methodHandle.invokeExact(source); } }
同时可以调整JMH的预热配置,确保JVM有足够时间完成优化(比如将预热次数增加到5次,每次1秒):
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
修正后,MethodHandle的性能应该会超过反射调用,符合其设计预期。
内容的提问来源于stack exchange,提问作者JackPGreen
相关产品推荐
相关产品推荐

