【Java踩坑笔记】27_大量使用反射,性能到底差多少?
2026/7/6 14:13:06 网站建设 项目流程

27 | 大量使用反射,性能到底差多少?

摘要:反射调用比直接调用慢 10~100 倍(取决于 JVM 是否做了优化)。如果高频调用,必须缓存Method对象,否则性能会很差。


一、问题现象

publicclassReflectionPerfTest{publicstaticvoidmain(String[]args)throwsException{Useruser=newUser("Alice");// 方式一:直接调用longstart=System.nanoTime();for(inti=0;i<100_0000;i++){user.getName();}System.out.println("直接调用:"+(System.nanoTime()-start)/1_000_000+"ms");// 方式二:反射调用(不缓存 Method)Methodmethod=User.class.getMethod("getName");start=System.nanoTime();for(inti=0;i<100_0000;i++){method.invoke(user);}System.out.println("反射调用(缓存Method):"+(System.nanoTime()-start)/1_000_000+"ms");// 方式三:反射调用(每次都 getMethod)start=System.nanoTime();for(inti=0;i<100_0000;i++){User.class.getMethod("getName").invoke(user);// ❌ 每次都 getMethod}System.out.println("反射调用(不缓存):"+(System.nanoTime()-start)/1_000_000+"ms");}}

运行结果(参考值,JDK 17):

直接调用:约 5ms 反射调用(缓存Method):约 50ms(慢 10 倍) 反射调用(不缓存):约 2000ms(慢 400 倍)

二、踩坑现场

场景 1:Spring 的反射调用

// ❌ 错误:每次都反射获取 Methodfor(Orderorder:orders){Methodm=order.getClass().getMethod("getStatus");Objectstatus=m.invoke(order);// 每次都 getMethod,性能极差}

场景 2:序列化框架里大量反射

很多 JSON 框架(如 FastJSON、Jackson)第一次序列化某个类时会用反射,但会缓存Field/Method,后续调用不会再反射查找。

但如果缓存失效(如用WeakReference缓存,被 GC 了),就会重新反射查找,导致性能抖动。


三、原理解析

3.1 反射调用为什么慢?

直接调用:编译期就确定了方法地址,调用时直接跳过去(CPU 分支预测友好)。

反射调用(Method.invoke()

  1. getMethod()需要遍历类的方法列表(O(n) 查找)
  2. invoke()内部需要检查访问权限(可访问性检查)
  3. invoke()需要用Varables包装参数(装箱开销)
  4. invoke()无法被内联优化(JVM 动态调用,JIT 难以优化)

3.2 JVM 对反射的优化

JDK 1.8+ 对反射做了优化:

  • Method.invoke()在调用一定次数后,会生成字节码代理(不再用 native 方法,性能接近直接调用)
  • 这个阈值可以通过-Dsun.reflect.inflationThreshold=N调整(默认 15)

:每次都getMethod()的查找开销依然无法避免。

3.3 缓存 Method 的效果

// ❌ 不缓存:每次都查找for(...){Methodm=clazz.getMethod("getName");// 每次 O(n) 查找m.invoke(obj);}// ✅ 缓存:只查找一次Methodm=clazz.getMethod("getName");// 只查找一次for(...){m.invoke(obj);// 直接调用,JVM 会优化}

性能差距:缓存后接近直接调用的 1~2 倍(JDK 17+)。


四、正确写法

4.1 缓存 Method / Field 对象

// ✅ 正确:缓存 MethodpublicclassBeanUtils{privatestaticfinalMap<Class<?>,Method[]>METHOD_CACHE=newConcurrentHashMap<>();publicstaticMethod[]getMethods(Class<?>clazz){returnMETHOD_CACHE.computeIfAbsent(clazz,Class::getMethods);// ✅ 缓存}}

4.2 用 MethodHandle(Java 7+,性能更好)

// ✅ MethodHandle 比 Method.invoke() 更快(更接近直接调用)publicclassMethodHandleTest{publicstaticvoidmain(String[]args)throwsThrowable{Useruser=newUser("Alice");MethodHandles.Lookuplookup=MethodHandles.lookup();MethodTypemt=MethodType.methodType(String.class);MethodHandlemh=lookup.findVirtual(User.class,"getName",mt);// 调用Stringname=(String)mh.invoke(user);// ✅ 性能接近直接调用}}

4.3 用 LambdaMetafactory(Java 8+,最快)

// ✅ 用 LambdaMetafactory 生成函数式接口,性能几乎等同于直接调用publicclassLambdaReflection{publicstaticvoidmain(String[]args)throwsException{MethodgetName=User.class.getMethod("getName");// 将 Method 转成 Function(只做一次,后续调用无反射开销)Function<User,String>getter=LambdaMetafactoryUtil.createGetter(getName);Useruser=newUser("Alice");Stringname=getter.apply(user);// ✅ 无反射开销}}

4.4 非必要不使用反射

// ❌ 可以用接口就不要用反射publicvoidprocess(Objectobj){Methodm=obj.getClass().getMethod("process");m.invoke(obj);}// ✅ 用接口publicinterfaceProcessable{voidprocess();}publicvoidprocess(Processableobj){obj.process();// ✅ 直接调用,编译期确定}

五、最佳实践

✅ 反射使用的 4 条规范

  1. 必须缓存Method/Field对象,不要每次都getMethod()/getField()
  2. 高频调用场景避免使用反射,用接口 + 多态代替
  3. 框架开发才用反射,业务代码尽量不用
  4. JDK 17+ 的反射性能已大幅改善,但依然比直接调用慢

🔍 反射性能对比(JMH 基准测试参考)

调用方式相对性能(倍数)
直接调用1x(基准)
反射(缓存 Method,JDK 17+)1.5x ~ 3x
反射(缓存 Method,JDK 8)5x ~ 10x
反射(不缓存 Method)100x ~ 400x

🛠️ 阿里巴巴 Java 开发手册规约

【强制】避免使用反射调用方法,如果避免不了,应当对Method对象进行缓存。


六、小结

  • 反射调用比直接调用慢10~400 倍(取决于是否缓存Method
  • getMethod()/getField()是主要性能瓶颈,必须缓存
  • JDK 17+ 对反射做了优化,缓存后性能接近直接调用(1.5x~3x)
  • 高频场景用MethodHandleLambdaMetafactory代替反射
  • 业务代码避免使用反射,用接口 + 多态代替

下一篇预告:finalize 方法已被标记废弃,还在用就快改——finalize()调用时机不确定,还会拖慢 GC,Java 9 起已标记废弃。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询