Method.invoke()底层通过懒加载的MethodAccessor实现调用,初始使用NativeMethodAccessorImpl经JNI执行,调用达15次后触发“膨胀机制”,动态生成纯Java版GeneratedMethodAccessor并替换委托,以平衡启动开销与长期性能。

Method.invoke(obj, args) 的底层并不直接执行方法调用,而是通过 MethodAccessor 接口的实现类来完成。这个接口对象不是在 Method 实例创建时就初始化好的,而是**按需懒加载生成**,且生成策略会随调用次数动态调整——核心在于 JVM 的“膨胀机制”(inflation)。
MethodAccessor 的初始获取:DelegatingMethodAccessorImpl 作为调度层
首次调用 invoke() 时,methodAccessor 字段为 null,触发 acquireMethodAccessor()。它通过 ReflectionFactory.newMethodAccessor(this) 创建一个默认实现:
- 返回的是 DelegatingMethodAccessorImpl,它本身不干活,只持有一个 delegate 引用;
- 该 delegate 初始被设为 NativeMethodAccessorImpl;
- 所以第一次 invoke → Delegating → NativeMethodAccessorImpl → JNI 调用 JVM C++ 层执行目标方法。
为什么不用纯 Java 实现起步?性能与启动开销的权衡
NativeMethodAccessorImpl 基于 JNI,每次调用都要跨越 Java/C++ 边界,有固定开销,但无需生成字节码、无需类加载,启动极快。适合冷启动或低频调用场景。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- JVM 默认设定了一个阈值:ReflectionFactory.inflationThreshold = 15(JDK 多数版本中为 15 次);
- 当同一个 Method 对象的 invoke 被连续调用满 15 次后,NativeMethodAccessorImpl 内部的 numInvocations 计数器触达阈值;
- 此时会触发“膨胀”:由 MethodAccessorGenerator 动态生成一个纯 Java 实现的子类(如 sun.reflect.GeneratedMethodAccessorN),编译为字节码并 defineClass 加载进 JVM;
- 随后 DelegatingMethodAccessorImpl 的 delegate 引用会被原子替换为这个新生成的 Java 版 accessor。
膨胀后的 Java 版 MethodAccessor 是怎么来的?
这个生成过程由 JVM 内部的 MethodAccessorGenerator 完成(位于 sun.reflect 包,非 public API):
立即学习“Java免费学习笔记(深入)”;
- 它根据目标 Method 的签名(类名、方法名、参数类型、返回类型、修饰符)拼装一段符合 JVM 字节码规范的 Java 方法逻辑;
- 生成的类形如 GeneratedMethodAccessor123,其 invoke() 方法里是硬编码的类型转换 + 直接方法调用(类似手写的桥接代码),无反射开销;
- 该类通过 Unsafe.defineAnonymousClass 或 ClassLoader.defineClass 注入运行时;
- 后续所有 invoke 调用都走这个纯解释/即时编译路径,性能接近直接调用。
如何控制是否启用膨胀?
可通过系统属性干预这一行为:
- -Dsun.reflect.noInflation=true:禁用膨胀,始终使用 NativeMethodAccessorImpl;
- -Dsun.reflect.inflationThreshold=N:自定义阈值(如设为 1 表示首次调用就生成 Java 版);
- 注意:这些是内部调试属性,生产环境一般不建议修改,默认 15 是 HotSpot 综合启动时间与长期性能的平衡点。

















