缓存 Method 对象能明显提升反射调用性能,因其避免重复的方法查找、签名匹配和访问检查开销;Method 轻量且线程安全,配合 setAccessible(true) 和参数预处理后,性能可达直接调用的 3–5 倍。

为什么缓存 Method 对象能明显提升反射调用性能
因为每次调用 Class.getMethod() 或 Class.getDeclaredMethod() 都会触发方法查找、签名匹配、访问检查等开销,底层还要解析字节码结构。而 Method 对象本身是轻量且线程安全的,一旦获取完成,后续调用 method.invoke() 的瓶颈就只剩参数封装和访问控制(后者可通过 setAccessible(true) 绕过)。
实操建议:
- 用
ConcurrentHashMap缓存Method,key 可以是class.getName() + "#" + methodName + "(" + paramTypesString + ")" - 避免在循环内反复调用
getMethod(),哪怕只调一次也值得缓存 - 注意:缓存前确保目标方法不会被热替换(如某些 DevTools 或 OSGi 场景),否则可能拿到过期引用
setAccessible(true) 真的必要吗?哪些情况可以省略
必要,除非目标方法本来就是 public 且在同包或子类中调用。JVM 对非 public 成员的反射访问默认走完整的安全管理器校验路径,开销比 public 方法高一个数量级。
常见错误现象:IllegalAccessException 或性能陡降(尤其在高频调用时)。
立即学习“Java免费学习笔记(深入)”;
使用场景与注意事项:
- private / protected / package-private 方法必须加
setAccessible(true)才能调用 - Java 9+ 模块系统下,如果类在未导出的模块中,
setAccessible(true)可能失败,需配合--add-opensJVM 参数 - 不是所有环境都允许关闭访问检查(如某些安全沙箱),上线前务必验证
缓存 Method 后,invoke() 还慢?可能是参数没处理好
反射调用慢的第二大原因是参数自动装箱、类型转换和数组复制。比如传入 int 却用 Integer 包装,或把原始类型数组转成 Object[] 再传进去。
实操建议:
- 提前准备好参数类型匹配的
Object[],避免在每次invoke()前动态构造 - 对基本类型参数,确保传入的是对应包装类(
int → Integer),JVM 不会自动帮你拆箱再装箱 - 如果调用目标固定且参数少,可考虑用 LambdaMetafactory 生成函数式接口代理,绕过反射调用开销(但复杂度上升)
直接调用 vs 缓存反射 vs MethodHandle:怎么选
直接调用最快,但丧失动态性;缓存反射在灵活性和性能间平衡得最好;MethodHandle 在 Java 7+ 提供更底层的调用能力,理论上接近直接调用,但初始化成本更高、调试困难。
性能/兼容性影响:
- 缓存反射 +
setAccessible(true):比直接调用慢约 3–5 倍(HotSpot 优化后),兼容所有 Java 6+ -
MethodHandle:首次查找慢,后续调用快(接近 1.5–2 倍),但 Java 7 才引入,Android 不完全支持 - 不缓存反射:比直接调用慢 10–50 倍,尤其在方法多、类加载频繁时
真实项目里,90% 的场景缓存 Method 就够用了——简单、可控、兼容性强。别一上来就想用 MethodHandle 或字节码生成,除非压测证明它真卡在那里。
容易被忽略的一点:缓存的 Method 对象持有对 declaring class 的强引用,如果类加载器可能被卸载(如 Web 应用重部署),要用 WeakReference 包一层,否则引发内存泄漏。











