Method.invoke() 比直接调用慢10–100倍,因JVM绕开所有优化:需解析签名、检查权限、参数校验与装箱拆箱、异常包装,且底层为JNI调用;缓存Method/Field可提升3–5倍性能,JDK9+还需注意模块系统限制。
为什么 Method.invoke() 比直接调用慢 10–100 倍
根本不是“多走几步”,而是 JVM 主动绕开了所有优化路径。直接调用能被 JIT 内联、消除栈帧、折叠常量;而 Method.invoke() 每次都得从头做四件事:解析方法签名(字符串匹配)、检查访问权限(哪怕已设 setAccessible(true))、参数类型校验+装箱拆箱、再把异常包装成 InvocationTargetException。这些全是解释执行阶段的开销,JIT 基本不碰它。
- 测试数据常见:直接调用耗时约
3–5 ns,未缓存反射调用常达280–300 ns - JDK 9+ 后更重:若目标类所在模块没
opens包,setAccessible(true)会直接抛InaccessibleObjectException,连尝试机会都没有 - 底层是 JNI 调用——最终落到 native 方法,这层切换本身就有固定成本
缓存 Method / Field 是唯一有效的起步优化
很多人以为“用了反射就注定慢”,其实 80% 的损耗来自重复查找。比如在循环里写 clazz.getMethod("xxx"),等于每次都在遍历方法表做字符串匹配;而缓存后,后续调用只走 invoke 路径,性能可提升 3–5 倍。
- 必须静态初始化或单例容器中缓存,避免多线程竞争下重复创建
-
Method和Field对象本身线程安全,但缓存容器要用ConcurrentHashMap或static final - 别只缓存
getMethod(),私有方法要用getDeclaredMethod()+setAccessible(true)一次到位
比反射快得多的替代方案:MethodHandle 和字节码生成
如果真要高频动态调用,Method.invoke() 就不该是首选。MethodHandle 是 JVM 原生支持的轻量级句柄,支持内联,JIT 能优化它;而 ByteBuddy 这类工具则彻底绕过反射,在运行时生成真实字节码——调用开销几乎等同于普通方法。