最近写框架,不可避免的碰到反射的问题。一直以来认为反射是相对比较慢的,干脆写个程序测试对比一下不同的反射方案及性能。
对于一个简单的对象:
publicstaticclass TestObject {
private Strings;
public String getS() {
returns;
}
publicvoid setS(String s) {
this.s = s;
}
}
我比较了四种方案:
1. 直接存取
o.setS("123");
o.getS();
这当然是最快的方法。
2. 通过反射存取
Methodsetter =getMethod(TestObject.class,"setS",new Class[] { String.class },false);
Methodgetter =getMethod(TestObject.class,"getS",new Class[] {},false);
setter.invoke(o,new Object[] {"123" });
getter.invoke(o,new Object[0]);
这应该是相对最慢的方法。但究竟慢多少,一会我们再看。
3. 利用CGLIB FastClass存取
CGLIB提供了一个简单的FastClass机制,据称比Java反射快上许多倍,咱们试一下看看。
FastClassfc = FastClass.create(TestObject.class);
FastMethodsetter =fc.getMethod("setS",new Class[] { String.class });
FastMethodgetter =fc.getMethod("getS",new Class[] {});
setter.invoke(o,new Object[] {"123" });
getter.invoke(o,new Object[0]);
除了类名不同以外,其余操作和JDK自带的反射API非常相似。
如果你运行并跟踪一下,很容易了解这种FastClass的机制是怎样工作的。CGLIB在运行时生成一个类,把反射的调用转换成直接调用。通过跟踪代码,很容易得到自动生成的二进制类,并用JAD反编译可得类似下面的代码:
publicclass FastMethod {
privatefinal FastClassowningClass;
privatefinalintmethodIndex;
public FastMethod(FastClass fc,int index) {
this.owningClass = fc;
this.methodIndex = index;
}
public Object invoke(Object obj, Object[] args)throws InvocationTargetException {
returnowningClass.invoke(methodIndex, obj, args);
}
}
class GeneratedFastClass_TestObjectextends FastClass {
public Object invoke(int methodIndex, Object o, Object[] params) {
switch (methodIndex) {
case METHOD_SET_S:
((TestObject) o).setS((String) params[0]);
break;
case ...
}
}
}
使用这种方法的唯一代价就是:需要为每个类生成一个FastClass的实现。
4. 通过反射存取,但避开Java安全检查
Methodsetter =getMethod(TestObject.class,"setS",new Class[] { String.class },false);
Methodgetter =getMethod(TestObject.class,"getS",new Class[] {},false);
setter.setAccessible(true);
getter.setAccessible(true);
setter.invoke(o,new Object[] {"123" });
getter.invoke(o,new Object[0]);
这是我从网上找到的技巧。
现在让我们就这四种方案,来测试一下性能。测试的方法很简单:循环1000万遍,比较时间。完整的测试程序如下:
import java.lang.reflect.Method;
import java.text.MessageFormat;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
publicabstractclass ReflectionPerformanceTest {
publicstaticvoid main(String[] args)throws Exception {
int max = 10000000;
directAccess.run("Direct access", max);
fastClass.run("CGLIB Fast Class", max);
reflection.run("JDK reflection", max);
reflectionWithoutSecurityCheck.run(
"JDK reflection without security check", max);
}
privatestatic Method getMethod(Class clazz, String name,
Class[] paramTypes,boolean noSecurityCheck) {
Method method;
try {
method = clazz.getMethod(name, paramTypes);
}catch (Exception e) {
thrownew IllegalArgumentException(e);
}
if (noSecurityCheck) {
method.setAccessible(true);
}
return method;
}
privatestatic ReflectionPerformanceTestdirectAccess =new ReflectionPerformanceTest() {
protectedvoid runOnce(TestObject o)throws Exception {
o.setS("123");
o.getS();
}
};
privatestatic ReflectionPerformanceTestfastClass =new ReflectionPerformanceTest() {
private FastClassfc = FastClass.create(TestObject.class);
private FastMethodsetter =fc.getMethod("setS",
new Class[] { String.class });
private FastMethodgetter =fc.getMethod("getS",new Class[] {});
protectedvoid runOnce(TestObject o)throws Exception {
setter.invoke(o,new Object[] {"123" });
getter.invoke(o,new Object[0]);
}
};
privatestatic ReflectionPerformanceTestreflection =new ReflectionPerformanceTest() {
private Methodsetter =getMethod(TestObject.class,"setS",
new Class[] { String.class },false);
private Methodgetter =getMethod(TestObject.class,"getS",
new Class[] {},false);
protectedvoid runOnce(TestObject o)throws Exception {
setter.invoke(o,new Object[] {"123" });
getter.invoke(o,new Object[0]);
}
};
privatestatic ReflectionPerformanceTestreflectionWithoutSecurityCheck
=new ReflectionPerformanceTest() {
private Methodsetter =getMethod(TestObject.class,"setS",
new Class[] { String.class },true);
private Methodgetter =getMethod(TestObject.class,"getS",
new Class[] {},true);
protectedvoid runOnce(TestObject o)throws Exception {
setter.invoke(o,new Object[] {"123" });
getter.invoke(o,new Object[0]);
}
};
publicvoid run(String desc,int max)throws Exception {
TestObject o =new TestObject();
long start = System.currentTimeMillis();
for (int i = 0; i < max; i++) {
runOnce(o);
}
System.out.println(MessageFormat.format(
"duration={1,number,####,###} ms - {0}",new Object[] { desc,
new Long(System.currentTimeMillis() - start) }));
}
protectedabstractvoid runOnce(TestObject o)throws Exception;
publicstaticclass TestObject {
private Strings;
public String getS() {
returns;
}
publicvoid setS(String s) {
this.s = s;
}
}
}
下面是在不同的JDK下的测试结果:(我的机器是Core 2 Dual 2.66MHz)
JDK 1.4.2
duration=122 ms - Direct access
duration=936 ms - CGLIB Fast Class
duration=3,809 ms - JDK reflection
duration=1,605 ms - JDK reflection without security check
JDK 1.5.0
duration=83 ms - Direct access
duration=655 ms - CGLIB Fast Class
duration=3,150 ms - JDK reflection
duration=896 ms - JDK reflection without security check
JDK 1.6.0
duration=113 ms - Direct access
duration=562 ms - CGLIB Fast Class
duration=2,384 ms - JDK reflection
duration=743 ms - JDK reflection without security check
上述数据画成图表更直观:(比较矮的是比较好的)
观察:
l 新一代的JDK比上一代JDK性能提升非常明显,只是JDK 1.6.0在直接访问时性能反常地输给JDK 1.5.0。
l 直接访问在性能上占有绝对的优势,约比Java反射快上20(JDK 1.6.0)到30倍(JDK 1.4.2)左右。
l CGLIB FastClass的效果还是不错的,比直接访问慢5(JDK1.6.0)到10倍(JDK 1.4.2),比Java反射快4倍左右。
l 另人惊异的是,如果把“安全检查”关掉,再调用Java反射,其结果和CGLIB FastClass极其接近,比普通的Java反射快3-4倍左右。没想到Java反射居然把大量时间用在这里了。
结论:
l Java反射比直接访问要慢20倍,但别忘了我们测试了1000万次。实际单次访问的绝对时间差别,还是极其微小的。
l CGLib FastClass的效果不错,然而你必须附带CGLIB这个类库,而且在运行时要承担动态生成大量类的时间和空间。
l 把“安全检查”关掉,再用正常的方法调用Java反射,可以取得和CGLIB FastClass极为接近的效果,但却少了CGLIB的代价。因此如果security策略许可的话,这是最好的方案。
联系客服