反射调用慢的根源在于CPU指令层面多出三步:访问控制检查(每次触发完整安全校验)、基本类型装箱拆箱(引发堆内存分配与类型检查)、动态方法查找与解析(无法JIT内联,依赖JNI桩和解释器层)。

反射调用慢,不是因为“写法高级”,而是它在 CPU 指令层面多走了好几步路。每次 method.invoke() 执行,JVM 并非直接跳转到目标方法,而要先完成一整套运行时验证与适配流程——这三步最耗资源:访问控制检查、基本类型装箱拆箱、动态方法查找。
访问控制检查:每次调用都触发安全校验
JVM 对每个反射调用都会执行完整的访问权限验证,哪怕目标是 public 方法。这个过程不是简单判断修饰符,而是:
- 检查调用栈帧中所有类的 ProtectionDomain 是否允许该访问
- 遍历 SecurityManager 的 checkPermission() 链(即使未启用自定义策略,基础校验仍存在)
- 对私有成员调用
setAccessible(true)可绕过此步,但仅限当前 Method/Field 实例,且需 SecurityManager 允许(默认 JDK17+ 启用强封装后受限)
装箱与拆箱:基本类型参数引发隐式堆内存操作
反射 API 统一以 Object[] 接收参数、以 Object 返回结果,导致原始类型必须经历完整装箱/拆箱链路:
- 调用
invoke(obj, 42, true)时:int 42 → 堆上新建 Integer 对象;boolean true → 堆上新建 Boolean 对象 - 返回值如
int length = (int) method.invoke(str):需从堆中读取 Integer 实例,并校验其 runtime 类型是否为 Integer(非 Number 子类会抛ClassCastException),再复制值回栈 - 该过程涉及至少两次内存分配(装箱)、一次类型检查(拆箱)、一次值拷贝,远超直接调用的寄存器传参开销
方法查找与动态解析:无法复用 JIT 内联路径
直接调用如 str.length() 在 JIT 编译后会被内联为几条 CPU 指令;而反射调用必须走完整解析流程:
- 首次调用时:通过方法签名在常量池中定位符号引用 → 解析为实际 Method 结构体 → 构建可执行句柄
- 后续调用仍需校验目标对象类型是否匹配(防止子类覆盖后签名变化)
- JIT 编译器无法将
method.invoke()内联,只能生成通用 JNI 调用桩(stub),中间经由 JVM 解释器层、本地方法接口、再到目标方法入口,指令路径长、分支预测失败率高
这三者叠加,使得单次反射调用的 CPU 指令数可达直接调用的 10–20 倍,尤其在高频小方法场景下,缓存失效和分支误预测进一步放大延迟。










