最近寫(xiě)框架,不可避免的碰到反射的問(wèn)題。一直以來(lái)認(rèn)為反射是相對(duì)比較慢的,干脆寫(xiě)個(gè)程序測(cè)試對(duì)比一下不同的反射方案及性能。
對(duì)于一個(gè)簡(jiǎn)單的對(duì)象:
public
static
class
TestObject {
private
String
s
;
public
String getS() {
return
s
;
}
public
void
setS(String s) {
this
.
s
= s;
}
}
我比較了四種方案:
1.
直接存取
o.setS(
"123"
);
o.getS();
這當(dāng)然是最快的方法。
2.
通過(guò)反射存取
Method
setter
=
getMethod
(TestObject.
class
,
"setS"
,
new
Class[] { String.
class
},
false
);
Method
getter
=
getMethod
(TestObject.
class
,
"getS"
,
new
Class[] {},
false
);
setter
.invoke(o,
new
Object[] {
"123"
});
getter
.invoke(o,
new
Object[0]);
這應(yīng)該是相對(duì)最慢的方法。但究竟慢多少,一會(huì)我們?cè)倏础?
3.
利用
CGLIB FastClass
存取
CGLIB
提供了一個(gè)簡(jiǎn)單的
FastClass
機(jī)制,據(jù)稱(chēng)比
Java
反射快上許多倍,咱們?cè)囈幌驴纯础?
FastClass
fc
= FastClass.
create
(TestObject.
class
);
FastMethod
setter
=
fc
.getMethod(
"setS"
,
new
Class[] { String.
class
});
FastMethod
getter
=
fc
.getMethod(
"getS"
,
new
Class[] {});
setter
.invoke(o,
new
Object[] {
"123"
});
getter
.invoke(o,
new
Object[0]);
除了類(lèi)名不同以外,其余操作和
JDK
自帶的反射
API
非常相似。
如果你運(yùn)行并跟蹤一下,很容易了解這種
FastClass
的機(jī)制是怎樣工作的。
CGLIB
在運(yùn)行時(shí)生成一個(gè)類(lèi),把反射的調(diào)用轉(zhuǎn)換成直接調(diào)用。通過(guò)跟蹤代碼,很容易得到自動(dòng)生成的二進(jìn)制類(lèi),并用
JAD
反編譯可得類(lèi)似下面的代碼:
public
class
FastMethod {
private
final
FastClass
owningClass
;
private
final
int
methodIndex
;
public
FastMethod(FastClass fc,
int
index) {
this
.
owningClass
= fc;
this
.
methodIndex
= index;
}
public
Object invoke(Object obj, Object[] args)
throws
InvocationTargetException {
return
owningClass
.invoke(
methodIndex
, obj, args);
}
}
class
GeneratedFastClass_TestObject
extends
FastClass {
public
Object invoke(
int
methodIndex, Object o, Object[] params) {
switch
(methodIndex) {
case
METHOD_SET_S:
((TestObject) o).setS((String) params[0]);
break
;
case
...
}
}
}
使用這種方法的唯一代價(jià)就是:需要為每個(gè)類(lèi)生成一個(gè)
FastClass
的實(shí)現(xiàn)。
4.
通過(guò)反射存取,但避開(kāi)
Java
安全檢查
Method
setter
=
getMethod
(TestObject.
class
,
"setS"
,
new
Class[] { String.
class
},
false
);
Method
getter
=
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]);
這是我從網(wǎng)上找到的技巧。
現(xiàn)在讓我們就這四種方案,來(lái)測(cè)試一下性能。測(cè)試的方法很簡(jiǎn)單:循環(huán)
1000
萬(wàn)遍,比較時(shí)間。完整的測(cè)試程序如下:
import
java.lang.reflect.Method;
import
java.text.MessageFormat;
import
net.sf.cglib.reflect.FastClass;
import
net.sf.cglib.reflect.FastMethod;
public
abstract
class
ReflectionPerformanceTest {
public
static
void
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);
}
private
static
Method getMethod(Class clazz, String name,
Class[] paramTypes,
boolean
noSecurityCheck) {
Method method;
try
{
method = clazz.getMethod(name, paramTypes);
}
catch
(Exception e) {
throw
new
IllegalArgumentException(e);
}
if
(noSecurityCheck) {
method.setAccessible(
true
);
}
return
method;
}
private
static
ReflectionPerformanceTest
directAccess
=
new
ReflectionPerformanceTest() {
protected
void
runOnce(TestObject o)
throws
Exception {
o.setS(
"123"
);
o.getS();
}
};
private
static
ReflectionPerformanceTest
fastClass
=
new
ReflectionPerformanceTest() {
private
FastClass
fc
= FastClass.
create
(TestObject.
class
);
private
FastMethod
setter
=
fc
.getMethod(
"setS"
,
new
Class[] { String.
class
});
private
FastMethod
getter
=
fc
.getMethod(
"getS"
,
new
Class[] {});
protected
void
runOnce(TestObject o)
throws
Exception {
setter
.invoke(o,
new
Object[] {
"123"
});
getter
.invoke(o,
new
Object[0]);
}
};
private
static
ReflectionPerformanceTest
reflection
=
new
ReflectionPerformanceTest() {
private
Method
setter
=
getMethod
(TestObject.
class
,
"setS"
,
new
Class[] { String.
class
},
false
);
private
Method
getter
=
getMethod
(TestObject.
class
,
"getS"
,
new
Class[] {},
false
);
protected
void
runOnce(TestObject o)
throws
Exception {
setter
.invoke(o,
new
Object[] {
"123"
});
getter
.invoke(o,
new
Object[0]);
}
};
private
static
ReflectionPerformanceTest
reflectionWithoutSecurityCheck
=
new
ReflectionPerformanceTest() {
private
Method
setter
=
getMethod
(TestObject.
class
,
"setS"
,
new
Class[] { String.
class
},
true
);
private
Method
getter
=
getMethod
(TestObject.
class
,
"getS"
,
new
Class[] {},
true
);
protected
void
runOnce(TestObject o)
throws
Exception {
setter
.invoke(o,
new
Object[] {
"123"
});
getter
.invoke(o,
new
Object[0]);
}
};
public
void
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) }));
}
protected
abstract
void
runOnce(TestObject o)
throws
Exception;
public
static
class
TestObject {
private
String
s
;
public
String getS() {
return
s
;
}
public
void
setS(String s) {
this
.
s
= s;
}
}
}
下面是在不同的
JDK
下的測(cè)試結(jié)果:(我的機(jī)器是
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
上述數(shù)據(jù)畫(huà)成圖表更直觀:(
比較矮的是比較好的
)
觀察:
l
新一代的
JDK
比上一代
JDK
性能提升非常明顯,只是
JDK 1.6.0
在直接訪問(wèn)時(shí)性能反常地輸給
JDK 1.5.0
。
l
直接訪問(wèn)在性能上占有絕對(duì)的優(yōu)勢(shì),約比
Java
反射快上
20
(
JDK 1.6.0
)到
30
倍(
JDK 1.4.2
)左右。
l
CGLIB FastClass
的效果還是不錯(cuò)的,比直接訪問(wèn)慢
5
(
JDK1.6.0
)到
10
倍(
JDK 1.4.2
),比
Java
反射快
4
倍左右。
l
另人驚異的是,如果把“安全檢查”關(guān)掉,再調(diào)用
Java
反射,其結(jié)果和
CGLIB FastClass
極其接近,比普通的
Java
反射快
3
-
4
倍左右。沒(méi)想到
Java
反射居然把大量時(shí)間用在這里了。
結(jié)論:
l
Java
反射比直接訪問(wèn)要慢
20
倍,但別忘了我們測(cè)試了
1000
萬(wàn)次。實(shí)際單次訪問(wèn)的絕對(duì)時(shí)間差別,還是極其微小的。
l
CGLib FastClass
的效果不錯(cuò),然而你必須附帶
CGLIB
這個(gè)類(lèi)庫(kù),而且在運(yùn)行時(shí)要承擔(dān)動(dòng)態(tài)生成大量類(lèi)的時(shí)間和空間。
l
把“安全檢查”關(guān)掉,再用正常的方法調(diào)用
Java
反射,可以取得和
CGLIB FastClass
極為接近的效果,但卻少了
CGLIB
的代價(jià)。因此如果
security
策略許可的話,這是最好的方案。