🧱 Java基础与集合篇 · 第11篇
主题:反射机制与性能优化
🚀 高频指数:★★★★★
🎯 你将收获:反射底层原理、Class 对象结构、Method.invoke() 的性能开销、反射缓存优化、Spring 如何大量使用反射。
💡 面试官非常爱问:“反射为什么慢?Spring 为什么离不开反射?”
一、关键问题导入
💬 面试官常问:
- Java 的反射是什么?底层怎么实现?
- 反射为什么慢?
- 如何优化反射性能?
- Spring 为什么离不开反射?
你只要能清晰回答这 4 条,反射八股你就掌握得比 90% 的开发者都好。
二、反射的定义:运行期操作类的能力
反射允许你在 运行期 动态获取类的信息并操作类成员,典型能力包括:
- 获取类的字节码结构:
Class<?> clazz - 创建对象:
clazz.newInstance()、constructor.newInstance() - 获取属性:
clazz.getDeclaredField("xxx") - 调用方法:
method.invoke(obj, args) - 动态代理底层依赖反射:Proxy.newProxyInstance
☕️ 一句话:
“反射让程序在运行期具备操作类型的能力。”
三、Class 对象的本质
Java 中所有类在加载后,都会在方法区(JDK8:元空间)中生成一个唯一的 Class 对象。
示例:
Class<?> clazz = String.class;
Class 对象包含:
- 类名
- 字段列表
- 方法列表
- 构造方法列表
- 注解信息
- 修饰符
- 父类、接口信息
一个类在 JVM 中只有一份 Class 对象。
四、反射为什么慢?(底层原因)
1️⃣ Method.invoke() 需要做安全性检查
反射调用方法前需要检查:
- 参数类型是否匹配
- 权限是否允许访问(private/protected)
- JVM 栈帧需要构造反射调用结构
2️⃣ 反射调用是间接调用
正常调用是直接调用:
obj.doSomething();
反射调用:
method.invoke(obj);
需要查找方法、构造调用帧、类型转换,开销更大。
3️⃣ 反射会导致 JVM 失去一些 JIT 优化机会
反射调用的方法无法轻易被:
- 内联(inline)
- 去虚拟化(devirtualization)
- JIT 编译优化
导致性能降低 10~100 倍。
五、如何优化反射性能?
1️⃣ 缓存 Method、Constructor、Field
不要重复调用 getDeclaredMethod。
错误示例:
method = clazz.getDeclaredMethod("doSomething");
method.invoke(obj);
正确示例:
static final Method METHOD = clazz.getDeclaredMethod("doSomething");
METHOD.setAccessible(true);
METHOD.invoke(obj);
2️⃣ setAccessible(true)
跳过安全检查,性能可提升 5~20 倍。
method.setAccessible(true);
3️⃣ 使用 MethodHandle(JDK7+)
MethodHandle 是 JVM 底层字节码级调用,比反射快数倍。
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(User.class, "getName", MethodType.methodType(String.class));
mh.invoke(user);
4️⃣ 使用 LambdaMetafactory 生成函数式调用(极快)
Spring 5 中大量使用,性能接近普通方法调用。
六、Spring 为什么大量使用反射?
因为框架需要“动态操作用户类”,例如:
| 场景 | 使用反射的原因 |
|---|---|
| 依赖注入(DI) | 动态获取构造器与字段注入 |
| AOP 代理 | Proxy.newProxyInstance 依赖反射 |
| Bean 初始化 | 调用 @PostConstruct 方法 |
| 处理注解 | 扫描 @Component、@Service |
| Controller 映射 | HandlerMethod 调用依赖反射 |
☕️ 总结:
框架需要动态性 → 动态性离不开反射。
七、反射常见使用场景
- JSON 反序列化(如 Jackson、FastJSON)
- JDBC ORM 框架(MyBatis、Hibernate)
- 依赖注入(Spring)
- 自动生成对象(BeanUtils)
- 自定义框架、插件系统、动态调用引擎
反射是所有“框架级开发”的基础技能。
八、反射与泛型擦除的关系
反射无法直接得到泛型类型,因为:
- 泛型在运行期被擦除
- 但可以通过
getGenericType()拿到参数化类型(Type)
示例:
Field field = User.class.getDeclaredField("list");
Type type = field.getGenericType();
这在 MyBatis 和 JSON 库中常用于处理 List 的实际类型。
九、面试官追问清单(非常高频)
| 面试问题 | 标准答案要点 |
|---|---|
| 什么是反射? | 运行期动态获取类结构并操作成员的机制。 |
| 反射为什么慢? | 安全检查、类型检查、间接调用、JIT 优化缺失。 |
| 如何优化反射? | setAccessible(true)、缓存 Method、MethodHandle、LambdaMetafactory。 |
| Spring 为什么使用反射? | 动态注入、代理、注解处理都需要运行期扫描。 |
| Class 对象是什么? | JVM 中每个类对应唯一的类元数据对象。 |
| 可以用反射修改 private 字段吗? | 可以,setAccessible(true) 后可绕过权限检查。 |
十、项目实践建议
- 对频繁调用的反射方法必须缓存 Method / Constructor;
- 框架开发中优先使用 MethodHandle;
- JSON 库解析时尽量预构建“字段映射表”;
- 工具类(BeanUtils)一定要避免每次反射获取字段;
- 对安全敏感场景禁用 setAccessible(true)。
十一、口诀记忆
☕️ “反射为动态,慢在检查;缓存提速,句柄更快。”
十二、小结
| 知识点 | 结论 |
|---|---|
| 反射定义 | 运行期操作类的机制 |
| 性能慢原因 | 安全检查 + 间接调用 + JIT 优化丢失 |
| 优化方式 | 缓存 + setAccessible(true) + MethodHandle |
| 框架依赖反射 | DI、AOP、注解、动态代理 |
| Class 对象 | JVM 中类的唯一描述 |
✅ 一句话总结:
“反射提供动态能力,性能靠缓存和句柄优化。”
1295

被折叠的 条评论
为什么被折叠?



