⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

面试考察点

两问合一,考的是”知其然且知其所以然”:

  1. 反射是什么 – 不是背定义,是能说清楚 Class 对象、Method、Field 这套 API 到底干了什么。
  2. 为什么慢,慢在哪 – 能不能拆到 JVM 层面:JIT 优化失效、安全检查、参数装箱拆箱。
  3. 灵活性 vs 性能的取舍 – 什么时候该用、什么时候不该用,有没有工程判断力。
  4. 框架底层原理 – Spring、MyBatis 底层全是反射,理解反射就理解了一半框架原理。

核心答案

反射 = 运行时拿到一个类的”说明书”,动态调用它的方法和字段。 编译期不知道具体类是啥也没关系,运行期照样能操作。

为什么慢?四个字:优化全废。

  1. JIT 内联失效 – 反射调用是通用分发器,JIT 看不到目标方法,没法内联。
  2. 安全检查 – 每次 invoke 都要做权限校验(setAccessible(true) 可跳过,但要慎用)。
  3. 运行时解析 – 方法名、参数类型在运行时字符串匹配查找,不是编译时绑定。
  4. 参数装箱拆箱 – invoke 接受 Object...,基本类型全得装箱,返回值再拆箱。

深度解析

原理/机制

JVM 加载每个类时,会在堆中创建唯一的 java.lang.Class 对象——相当于这个类的”蓝图”,包含所有结构信息:Field、Method、Constructor、父类、接口。反射 API(java.lang.reflect 包)就是操作这个 Class 对象来”反推”类的结构并与之交互。

代码示例:反射 vs 正常调用

import java.lang.reflect.Method;

public class ReflectionDemo {

// 目标方法:纯计算,避免 I/O 干扰基准测试
public static int add(int a, int b) {
return a + b;
}

public static void main(String[] args) throws Exception {
int times = 5_000_000;
long start, end;

// ===== 1. 正常调用:编译时绑定,JIT 可内联优化 =====
start = System.nanoTime();
for (int i = 0; i < times; i++) {
add(i, i); // JIT 会把这个方法直接内联到循环里
}
end = System.nanoTime();
System.out.println("正常调用: " + (end - start) / 1_000_000 + " ms");

// ===== 2. 反射调用(不缓存 Method):每次都查找 + invoke =====
start = System.nanoTime();
for (int i = 0; i < times; i++) {
Method method = ReflectionDemo.class.getMethod("add", int.class, int.class);
method.invoke(null, i, i); // int 自动装箱为 Integer,再包成 Object[]
}
end = System.nanoTime();
System.out.println("反射(无缓存): " + (end - start) / 1_000_000 + " ms");

// ===== 3. 反射调用(缓存 Method + 关闭安全检查):最重要的优化 =====
Method cached = ReflectionDemo.class.getMethod("add", int.class, int.class);
cached.setAccessible(true); // 跳过权限检查,显著提速
start = System.nanoTime();
for (int i = 0; i < times; i++) {
cached.invoke(null, i, i); // 省了查找开销,但装箱和 Native 调用开销还在
}
end = System.nanoTime();
System.out.println("反射(缓存+setAccessible): " + (end - start) / 1_000_000 + " ms");
}
}

典型结果(仅供参考,JVM 版本和预热状态会影响数值):

正常调用: 3 ms
反射(无缓存): 682 ms
反射(缓存+setAccessible): 95 ms

结论:缓存 + setAccessible(true) 能快 7 倍,但和直接调用比仍差一个数量级。

为什么反射慢?逐层拆解

  1. 方法内联失效 – JIT 的核心优化是把热点方法代码”内联”到调用处。但 Method.invoke 是通用分发器,JIT 看不到目标方法,内联直接废了。

  2. 运行时查找 – getMethod 要在类元数据里做字符串匹配。缓存 Method 对象能省掉这步,但 invoke 本身的开销还在。

  3. Native 调用 + 安全检查 – invoke 最终走 Native 实现,Java 层到 Native 层有上下文切换开销。默认还要做 AccessibleObject.checkAccess 权限校验。

  4. 参数装箱拆箱 – invoke 接受 Object...,基本类型全装箱。JVM 还要拆数组、校验参数类型、传给 Native 方法。返回基本类型再拆箱。来回折腾,开销不小。

最佳实践与常见误区

最佳实践:

  1. 缓存 Method/Field 对象 – 最重要的优化。别在循环里重复 getMethod,查一次缓存起来。
  2. setAccessible(true) 慎用 – 关闭安全检查能显著提速(JDK 8 尤其明显),但会破坏封装性。明确知道后果再用。
  3. 业务代码别滥用反射 – 框架开发、动态代理、注解处理器该用就用。普通 CRUD 代码写反射是自虐。
  4. JDK 9+ 考虑 MethodHandle – java.lang.invoke 包下的替代方案,JVM 优化支持更好,某些场景性能优于反射。

常见误区:

  • “反射绝对不能用” – 错。Spring 和 MyBatis 底层全靠它,关键是用对场景。
  • “缓存了 Method 就不慢了” – 不准确。缓存只省了查找开销,invoke 本身的装箱、安全检查、Native 调用开销还在,和直接调用仍有显著差距。
文章目录
  1. 1. 面试考察点
  2. 2. 核心答案
  3. 3. 深度解析
    1. 3.1. 原理/机制
    2. 3.2. 代码示例:反射 vs 正常调用
    3. 3.3. 为什么反射慢?逐层拆解
    4. 3.4. 最佳实践与常见误区