文章目录
反射是否影响性能
为了放大问题找到共性,采用逐渐扩大测试次数(executeCount)、每次测试多次取平均值(averageCount)的方式。针对同一个属性分别就直接调用方法操作属性、反射调用方法操作属性、直接操作属性、反射操作属性,分别从1-1000000,每个数量级测试一次:
public class Person {
public int age;
public Person(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionPerformanceActivity {
public static void main(String[] args) throws Exception {
//总共执行次数: 单次耗时时间短, 多执行几次, 放大耗时差距
int executeCount = 1;
//平均次数: 多进行几轮实验取平均值, 排除偶发干扰
int averageCount = 10;
long reflectMethodCostTime=0;
long normalMethodCostTime=0;
long reflectFieldCostTime=0;
long normalFieldCostTime=0;
for (int i = 0; i < averageCount; i++) {
reflectMethodCostTime += getReflectCallMethodCostTime(executeCount);
normalMethodCostTime += getNormalCallMethodCostTime(executeCount);
reflectFieldCostTime += getReflectCallFieldCostTime(executeCount);
normalFieldCostTime += getNormalCallFieldCostTime(executeCount);
}
System.out.println("执行直接调用方法总耗时:" + normalMethodCostTime/averageCount + " 毫秒");
System.out.println("执行反射调用方法总耗时:" + reflectMethodCostTime/averageCount + " 毫秒");
System.out.println("执行普通调用实例总耗时:" + normalFieldCostTime/averageCount + " 毫秒");
System.out.println("执行反射调用实例总耗时:" + reflectFieldCostTime/averageCount + " 毫秒");
}
/**
* 反射操作方法总耗时
* @param count
* @return
*/
private static long getReflectCallMethodCostTime(int count) throws Exception {
long startTime = System.currentTimeMillis();
Person user = new Person(12);
for(int index = 0 ; index < count; index++){
Method setAge = Person.class.getMethod("setAge", int.class);
setAge.setAccessible(true);
setAge.invoke(user, index);
}
return System.currentTimeMillis() - startTime;
}
/**
* 反射操作属性总耗时
* @param count
* @return
*/
private static long getReflectCallFieldCostTime(int count) throws Exception {
long startTime = System.currentTimeMillis();
Person user = new Person(12);
for(int index = 0 ; index < count; index++){
Field ageField = Person.class.getDeclaredField("age");
ageField.set(user, index);
}
return System.currentTimeMillis()-startTime;
}
/**
* 实例直接操作方法总耗时
*/
private static long getNormalCallMethodCostTime(int count){
long startTime = System.currentTimeMillis();
Person user = new Person(12);
for(int index = 0 ; index < count; index++){
user.setAge(index);
}
return System.currentTimeMillis() - startTime;
}
/**
* 实例直接操作属性总耗时
*/
private static long getNormalCallFieldCostTime(int count){
long startTime = System.currentTimeMillis();
Person user = new Person(12);
for(int index = 0 ; index < count; index++){
user.age = index;
}
return System.currentTimeMillis() - startTime;
}
}
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9e2J5Mc-1637682940030)(https://note.youdao.com/yws/public/resource/4a01e14b87652ad2c38721ffff0e5c3a/xmlnote/3170F7D325D3432BBE9F78D4D5270DE3/23906)]
测试结论:
-
反射的确会导致性能问题;
-
反射导致的性能问题是否严重跟使用的次数有关系。
-
如果控制在1000次以内,基本上没什么差别;如果调用次数超过了1000次,性能差异会很明显;调用次数越多性能差异越大;
-
四种访问方式中:
直接操作属性的方式效率最高;
其次是直接调用方法操作属性的方式,耗时约为直接操作属性的1.2倍;
接着是通过反射调用方法操作属性的方式,耗时约为直接操作属性的17172倍;
最慢的是通过反射调用方法操作属性的方式,耗时约为直接操作属性的25993倍。
为什么反射影响性能
分析反射相关代码,由于进行上述实验时,没有启用Java安全机制,因此,对于反射方法setAge.setAccessible(true)
中处理逻辑也只是设置属性值而已,不会产生明显的性能差异。
那么,具体的性能差异应该就在这几个方法中:Class.getMethod
、method.invoke
、Class.getDeclaredField
、field.set
method.invoke & field.set 性能
对上述getReflectCallMethodCostTime
和getReflectCallFieldCostTime
做如下改造:
/**
* 反射操作方法总耗时
* @param count
* @return
*/
private static long getReflectCallMethodCostTime(int count) throws Exception {
long startTime = System.currentTimeMillis();
Person user = new Person(12);
Method setAge = Person.class.getMethod("setAge", int.class);
setAge.setAccessible(true);
for(int index = 0 ; index < count; index++){
setAge.invoke(user, index);
}
return System.currentTimeMillis() - startTime;
}
/**
* 反射操作属性总耗时
* @param count
* @return
*/
private static long getReflectCallFieldCostTime(int count) throws Exception {
long startTime = System.currentTimeMillis();
Person user = new Person(12);
Field ageField = Person.class.getDeclaredField("age");
for(int index = 0 ; index < count; index++){
ageField.set(user, index);
}
return System.currentTimeMillis()-startTime;
}
测试结果:
Class.getMethod & Class.getDeclareField 性能
再次对上述getReflectCallMethodCostTime
和getReflectCallFieldCostTime
做如下改造:
/**
* 反射操作方法总耗时
* @param count
* @return
*/
private static long getReflectCallMethodCostTime(int count) throws Exception {
long startTime = System.currentTimeMillis();
Method setAge = null;
for(int index = 0 ; index < count; index++){
setAge = Person.class.getMethod("setAge", int.class);
}
setAge.setAccessible(true);
return System.currentTimeMillis() - startTime;
}
/**
* 反射操作属性总耗时
* @param count
* @return
*/
private static long getReflectCallFieldCostTime(int count) throws Exception {
long startTime = System.currentTimeMillis();
Field ageField = null;
for(int index = 0 ; index < count; index++){
ageField = Person.class.getDeclaredField("age");
}
return System.currentTimeMillis()-startTime;
}
测试结果:
测试结论
method.invoke
和field.set
相对于普通直接操作属性速度要慢了许多,其中,后者的耗时是前者的1.4倍;Class.getMethod
和Class.getDeclaredField
相对于 1 性能更差,前者耗时是或者的1.6倍;- 反射方法
Class.getMethod
和Class.getDeclaredField
比method.invoke
和field.set
耗时多的多,从实验结果来看,至少是2个数量级的差距; - 随着测试数量级越大,性能差异的比例越趋于稳定。
综上所述,影响反射性能的主要原因是这两个方法:Class.getMethod
和Class.getDeclaredField
反射如何影响性能
由于测试的这四个方法最终调用的都是native
方法,无法进一步跟踪。猜测应该是和在程序运行时操作class
有关:
- 比如需要判断是否安全?是否允许这样操作?入参是否正确?是否能够在虚拟机中找到需要反射的类?主要是这一系列判断条件导致了反射耗时;
- 也有可能是因为调用
natvie
方法,需要使用JNI
接口,导致了性能问题。参照Log.java
、System.out.println
都是调用native
方法,重复调用多次耗时很明显。
如何避免反射导致的性能问题
- 不要过于频繁地、大量地使用反射;
- 通过 反射直接操作属性 会比 反射调用方法间接操作属性 快很多,如果要使用反射的话,应该优先采用 反射直接操作属性 的方式。
其他问题
- 丧失了编译时类型检查的好处,包含异常检查;
- 执行反射访问所需要的代码非常笨拙和冗长;
- 性能损失;
- 对性能敏感的接口尽量不使用反射或者少使用反射;
参考: