反射频繁调用会因隐式对象创建加剧年轻代抖动,应缓存method/field、使用methodhandle替代、避免临时数组与装箱,并在编译期用字节码生成等方案替代运行时反射。

反射本身不直接分配大量对象,但频繁调用(尤其是Class.getMethod()、Method.invoke()等)会触发隐式对象创建,加剧年轻代抖动。关键在于:反射调用路径上每一步都可能生成临时包装类、参数数组、异常对象、缓存键值对,而这些对象生命周期极短,集中落入Eden区,很快被Minor GC回收——形成“创建→几毫秒后丢弃”的高频锯齿曲线。
缓存反射元数据,避免重复解析
每次Class.getMethod("xxx")都会遍历方法表并构造Method实例;Field.get(obj)同样会触发字段查找与安全检查。应将结果缓存,复用已有引用:
- 用
ConcurrentHashMap<string method></string>按类+方法签名缓存Method,首次调用后后续直接get - 避免在循环体内调用
clazz.getDeclaredMethod(),提取到方法外初始化 - 缓存的
Method需设为setAccessible(true)一次,避免每次invoke都触发访问检查开销和临时SecurityManager相关对象
用MethodHandle替代反射API(Java 7+)
MethodHandle是JVM原生支持的轻量级调用机制,比Method.invoke()更高效:
- 无自动装箱/拆箱开销(
invokeExact要求类型严格匹配) - 不抛出
InvocationTargetException,异常直接透出,省去包装异常对象 - 可静态获取并复用:
MethodHandles.lookup().findVirtual(...)只做一次,后续mh.invokeExact(obj, args...)几乎零对象分配
规避反射调用中的隐式数组与装箱
常见抖动源头是Method.invoke(obj, args)的args参数:
- 传入单个参数却写成
method.invoke(obj, new Object[]{val})——每次新建Object数组 - 基本类型参数被自动装箱,如
invoke(obj, 123)→ 创建Integer实例 - 改用类型专用invoke:对单参方法,优先用
invokeExact(obj, intVal)(配合MethodHandle);必须用反射时,复用预分配的Object[1]数组并重用
限制反射使用范围,用编译期替代方案
反射不是万能解药,高频路径应主动规避:
- 序列化/反序列化场景:用
Unsafe.defineAnonymousClass或字节码生成(如Byte Buddy)生成静态访问器,彻底消除运行时反射 - 依赖注入/框架回调:启用JDK的
@CallerSensitive感知或模块化服务定位,减少泛型反射查找 - Android等资源受限环境:用
AnnotationProcessor在编译期生成XXXAccessor类,运行时纯方法调用











