bigywater.tech
Java · 157

反射与 MethodHandle:一个方法调用如何变成调用点

反射、MethodHandle 与直接调用到底差多少?这篇先看 MethodHandle 的类型与访问规则,再用同一个 add 方法在四种调用方式下各调用 2000 万次(反射本身的开销已有专门的文章,MethodHandle 组合器在后面)。

0.31 vs 2.34 ns
static final 与非 final 字段里的 MethodHandle.invokeExact
2.76 ns
Method.invoke 每次调用耗时
WrongMethodTypeException
invokeExact 的类型与句柄不一致时抛出

类型与调用方式

实测 · 本机执行输出
  句柄的类型 = (Calc,int,int)int(接收者变成第一个参数)
  invokeExact((int) …) = 5
  invokeExact 但结果当 Object 用 → WrongMethodTypeException: handle's method type (Calc,int,int)int but found (Calc,int,int)Object
  invokeExact 传 Integer 而不是 int → WrongMethodTypeException: handle's method type (Calc,int,int)int but found (Calc,Integer,int)int
  invoke(自动装箱拆箱适配) = 5
  bindTo(c) 后的类型 = (int,int)int,调用 bound.invoke(10, 20) = 30
  greet 句柄 asType 成 (Object)Object 后调用 = hi ann
  Lookup 找另一个类 Other 的私有方法 secret → IllegalAccessException: no such method: Other.secret(int)int/invokeVirtual
  方法不存在 → NoSuchMethodException: no such method: Main$Calc.nope()void/invokeVirtual
  privateLookupIn(Other.class, lookup) 之后找 secret 并调用 = 42

访问检查

实测 · 本机执行输出
  直接调用                                         预热后每次调用 0.31 ns(后 5 轮中位数)
  Method.invoke(缓存的 Method)                    预热后每次调用 2.76 ns(后 5 轮中位数)
  static final MethodHandle.invokeExact        预热后每次调用 0.31 ns(后 5 轮中位数)
  非 final 字段里的 MethodHandle.invokeExact        预热后每次调用 2.34 ns(后 5 轮中位数)
  static final MethodHandle.invoke             预热后每次调用 0.31 ns(后 5 轮中位数)

分步演示

MethodHandle
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:JMH 严格基准(本篇是自写的循环计时,包含预热但没有防止所有 JIT 优化的手段)、JDK 17 与 27 的数字、MethodHandle 在 spread/collect 等变换后的开销、反射在 JDK 18+ 用 MethodHandle 重写后的变化、setAccessible 的影响
    京ICP备2026067502号-1京公网安备11010502063487号