Java反射的性能问题
反射是Java一个很重要的动态特性,许多常用框架都用到了Java的反射。想要详细了解一下Java的反射相比直接调用有多少的性能损耗,效率降低的原因在哪里,以及如何对反射进行优化,自己总结了一下。
1 反射有哪些方法?
老生常谈了,三种方法,分别是:
-
.class字段
Foo foo = new Foo(); Class c1 = foo.class;
-
getClass()方法
Foo foo = new Foo(); Class c2 = foo.getClass();
-
forName()方法
Foo foo = new Foo(); Class c3 = Class.forName("Foo的包路径");
得到Class对象之后,可以通过newInstance()的方法创建新的实例:
Foo newIns = c1.newInstance();
2 反射的效率损失?
这里直接放上反射的性能问题这篇Blog里的实验数据:
场景 | 本机测试结果(XP,双核,2G) | 服务器测试结果(Linux,XEN虚拟机,8核,5.5G) |
---|---|---|
方法直接调用 | 235MS | 190MS |
JDK Method调用 | 29188MS | 4633MS |
JDK Method调用(稍作优化) | 5672MS | 4262MS |
Cglib FastMethod调用 | 5390MS | 2787MS |
可以看到,即使是经过优化,反射仍然比直接调用要慢上一个数量级。
3 影响反射效率的原因?
那么反射效率低的原因在哪里呢?这个问题网上一直说的含糊不清,应该是和每个版本Java的反射的具体实现方式有关。实际上,Java对反射在不断地进行优化,反射的性能一直在提高。
Java反射获取方法
流程是这样的:
-
反射需要获取类的所有方法,得到一个Method数组,包含着每个方法的参数,返回值类型,权限等信息;
-
需要遍历Method数组,得到我们需要调用的那个方法,返回其拷贝,接下来我们调用其拷贝;
-
通过invoke来调用拷贝的方法,在调用之前,我们要先检查是否有权限执行该方法;
-
调用方法需要对参数进行解封,因为invoke的参数类型为Object,需要将其解封为实际的参数类型;
-
反射需要动态加载,因而无法对其进行即时(JIT)优化。
反射的效率损失主要集中在以上几个方面。
4 如何进行优化?
针对以上几点,主要的优化方法有:
- 利用缓存,将反射得到的元数据缓存起来,使用时直接从内存中访问。
- 不要用getMethods()方法后再遍历,直接用getMethod(methodName)来根据方法名获取方法。
- 使用高性能的反射库
- setAccessible(true),禁用安全检查,这会提升很大的效率
实际上,随着JDK版本的优化,很多工作无需我们自己做,大多数情况下,只需要手动设置setAccessible(true)即可实现反射的高性能。