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 这类工具则彻底绕过反射,在运行时生成真实字节码——调用开销几乎等同于普通方法。
-
MethodHandle示例:lookup.findVirtual(User.class, "getName", methodType(String.class)),之后mh.invoke(obj)即可 - 注意:
MethodHandle创建仍有开销,仍需缓存,但它 invoke 的部分确实快得多 - ByteBuddy 等方案适合框架级使用(如序列化、RPC),应用层一般没必要自己上,除非压测确认反射成了瓶颈
JDK 9+ 下 setAccessible(true) 不再“万能”
这不是性能问题,是运行失败风险。JDK 9 引入模块系统后,setAccessible(true) 只在目标包被 opens(运行时开放)或 exports(编译时导出)时才生效。否则直接抛异常,且 JVM 会打印警告日志,某些容器环境甚至静默拒绝。
立即学习“Java免费学习笔记(深入)”;
- 常见报错:
java.lang.reflect.InaccessibleObjectException: Unable to make field private java.util.ArrayList.elementData accessible - 临时解法(仅开发):
--add-opens java.base/java.util=ALL-UNNAMED启动参数 - 长期方案:改用
VarHandle(JDK 9+)操作字段,或接受限制,改用公有 API










