反射调用比直接调用慢5–10倍(部分达30–100倍),因每次调用均绕过编译期优化,强制执行运行时解析、权限校验、参数装箱拆箱、Object[]堆分配、JIT放弃内联及多次虚方法分派等结构性开销。

反射调用在百万级调用下,延迟通常是直接调用的 5–10 倍,部分实测场景甚至达到 30–100 倍。这个差距不是线性增长,而是由 JVM 底层执行机制决定的结构性开销,不是“写法问题”,而是每次调用都绕过了编译期优化路径。
真实压测数据参考(百万次空方法调用)
多个独立测试环境(JDK 8–17,i7/AMD Ryzen,Windows/Linux)给出的一致趋势:
- 直接调用:约 4–12ms
- 传统反射(
Method.invoke()):约 65–365ms(常见值 200ms 左右) - 缓存 Method + setAccessible(true):可降至 45–90ms,仍比直接调用慢 5–8 倍
- 表达式树编译(
Expression.Lambda或 JavaMethodHandle):约 5–15ms,逼近直接调用
为什么百万次才明显?关键在“首次开销”和“累积效应”
单次反射调用本身耗时可能只有几十纳秒,但问题出在高频重复触发的固定成本上:
- 每次
invoke()都要走权限校验链(即使 public 方法,AccessControlContext检查不可跳过) - 参数数组
Object[]必须堆分配,百万次 = 百万次 GC 压力 - JIT 编译器放弃内联、逃逸分析、常量传播 —— 热点代码无法升为机器码
- 首次调用还会触发
NativeMethodAccessorImpl → GeneratedMethodAccessor膨胀,生成字节码并加载类,这一过程独占 1–3ms
哪些操作真正拖慢了反射?
不是“反射”这个概念慢,而是具体环节叠加放大:
- 运行时符号解析:每次从字符串名查方法,而非字节码里硬编码的符号引用
-
装箱/拆箱强制发生:
invoke(obj, new Object[]{42})中 int → Integer → int,三步转换 -
安全框架全程参与:哪怕禁用安全管理器(
-Djava.security.manager=disabled),JVM 内部访问控制逻辑仍存在 - 间接调用层级深:Method → MethodAccessor → 目标方法,至少两次虚方法分派(vtable lookup)
压测时容易忽略的关键点
只比“调用时间”会误判,真实延迟还藏在这些地方:
- 预热不足:JVM JIT 需要数千次调用才能开始编译,未预热的反射结果偏高 20%–50%
- GC 干扰:反射高频创建临时数组,Full GC 可能穿插其中,拉高 P99 延迟
- 对象生命周期:反射创建的
Method、Constructor实例若未复用,反复查找会重复元数据解析 - 字段 vs 方法差异:反射读字段比调方法快不少(慢 2–3 倍),但写 private 字段反而更慢(需额外修饰符位运算)










