一直以來都在說反射慢辙培,但是根本沒有具體測試過蔑水,也沒感受過
反射真的慢嗎?
參考:http://www.reibang.com/p/4e2b49fa8ba1
是的扬蕊,很慢肤粱!
下圖是一億次循環(huán)的耗時:
- 直接調(diào)用 100000000 times using 36 ms
- 原生反射(只invoke) 100000000 times using 325 ms
- 原生反射(只getMethod) 100000000 times using 11986 ms
- 原生反射(緩存Method) 100000000 times using 319 ms
- 原生反射(沒有緩存Method) 100000000 times using 12169 ms
- reflectAsm反射優(yōu)化(緩存Method) 100000000 times using 43 ms
- reflectAsm反射優(yōu)化(沒有緩存Method) 100000000 times using 131788 ms
沒有一個可以比 直接調(diào)用 更快的。
- 原生反射(沒有緩存Method) 大概比 直接調(diào)用 慢了 340倍
- 原生反射(緩存Method) 大概比 直接調(diào)用 慢了 9倍
怎么優(yōu)化速度厨相?
反射的速度差異只在大量連續(xù)使用才能明顯看出來,理論上100萬次才會說反射很慢鸥鹉,對于一個單進(jìn)單出的請求來說蛮穿,反射與否根本差不了多少。
這樣就沒必要優(yōu)化了嗎毁渗,并不是践磅。
事實上各大框架注解,甚至業(yè)務(wù)系統(tǒng)中都在使用反射灸异,不能因為慢就不用了府适。
在后臺Controller中序列化請求響應(yīng)信息大量使用注解羔飞,高并發(fā)就意味著連續(xù)百萬級別調(diào)用反射成為可能,各大MVC框架都會著手解決這個問題檐春,優(yōu)化反射逻淌。
反射核心的是getMethod和invoke了,分析下兩者的耗時差距疟暖,在一億次循環(huán)下的耗時卡儒。
Method getName = SimpleBean.class.getMethod("getName");
getName.invoke(bean);
原生反射(只invoke) 100000000 times using 221 ms
原生反射(只getMethod) 100000000 times using 12849 ms
優(yōu)化思路1:緩存Method,不重復(fù)調(diào)用getMethod
證明getMethod很耗時俐巴,所以說我們要優(yōu)先優(yōu)化getMethod骨望,看看為什么卡?
Method getName = SimpleBean.class.getMethod("getName");
//查看源碼
Method res = privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
//再看下去
private native Field[] getDeclaredFields0(boolean publicOnly);
private native Method[] getDeclaredMethods0(boolean publicOnly);
private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly);
private native Class<?>[] getDeclaredClasses0();
getMethod最后直接調(diào)用native方法欣舵,無解了擎鸠。想復(fù)寫優(yōu)化getMethod是不可能的了,官方?jīng)]毛病缘圈。
但是我們可以不需要每次都getMethod啊劣光,我們可以緩存到redis,或者放到Spring容器中准验,就不需要每次都拿了赎线。
//通過Java Class類自帶的反射獲得Method測試,僅進(jìn)行一次method獲取
@Test
public void javaReflectGetOnly() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Method getName = SimpleBean.class.getMethod("getName");
Stopwatch watch = Stopwatch.createStarted();
for (long i = 0; i < times; i++) {
getName.invoke(bean);
}
watch.stop();
String result = String.format(formatter, "原生反射+緩存Method", times, watch.elapsed(TimeUnit.MILLISECONDS));
System.out.println(result);
}
- 原生反射(緩存Method) 100000000 times using 319 ms
- 原生反射(沒有緩存Method) 100000000 times using 12169 ms
緩存Method大概快了38倍糊饱,離原生調(diào)用還差個9倍垂寥,所以我們繼續(xù)優(yōu)化invoke。
優(yōu)化思路2:使用reflectAsm另锋,讓invoke變成直接調(diào)用
我們看下invoke的源碼:
getName.invoke(bean);
//查看源碼
private static native Object invoke0(Method var0, Object var1, Object[] var2);
尷尬滞项,最后還是native方法,依然沒毛病夭坪。
invoke不像getMethod可以緩存起來重復(fù)用文判,沒法優(yōu)化。
所以這里需要引入ASM室梅,并做了個工具庫reflectAsm:
參考:https://blog.csdn.net/zhuoxiuwu/article/details/78619645戏仓,https://github.com/EsotericSoftware/reflectasm
“ASM 是一個 Java 字節(jié)碼操控框架。它能被用來動態(tài)生成類或者增強既有類的功能亡鼠。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件赏殃,也可以在類被加載入 Java 虛擬機(jī)之前動態(tài)改變類行為。Java class 被存儲在嚴(yán)格格式定義的 .class 文件里间涵,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱仁热、方法、屬性以及 Java 字節(jié)碼(指令)勾哩。ASM 從類文件中讀入信息后抗蠢,能夠改變類行為举哟,分析類信息,甚至能夠根據(jù)用戶要求生成新類迅矛》列桑”
使用如下:
MethodAccess methodAccess = MethodAccess.get(SimpleBean.class);
methodAccess.invoke(bean, "getName");
//看看MethodAccess.get(SimpleBean.class)源碼,使用了反射的getMethod】
Method[] declaredMethods = type.getDeclaredMethods();
參考:https://blog.csdn.net/z69183787/article/details/51657771
invoke是沒辦法優(yōu)化的诬乞,也沒辦法做到像直接調(diào)用那么快册赛。所以大佬們腦洞大開,不用反射的invoke了震嫉。原理如下:
- 借反射的getDeclaredMethods獲取SimpleBean.class的所有方法森瘪,然后動態(tài)生成一個繼承于MethodAccess 的子類SimpleBeanMethodAccess,動態(tài)生成一個Class文件并load到JVM中票堵。
- SimpleBeanMethodAccess中所有方法名建立index索引扼睬,index跟方法名是映射的,根據(jù)方法名獲得index悴势,SimpleBeanMethodAccess內(nèi)部建立的switch直接分發(fā)執(zhí)行相應(yīng)的代碼窗宇,這樣methodAccess.invoke的時候,實際上是直接調(diào)用特纤。
實際上reflectAsm是有個致命漏洞的军俊,因為要生成文件,還得load進(jìn)JVM捧存,所以reflectAsm的getMethod特別慢:
- reflectAsm反射優(yōu)化(沒有緩存Method) 100000000 times using 131788 ms
雖然getMethod很慢粪躬,但是invoke的速度是到達(dá)了直接調(diào)用的速度了。
如果能夠緩存method昔穴,那么reflectAsm的速度跟直接調(diào)用一樣镰官,而且能夠使用反射!
- 直接調(diào)用 100000000 times using 36 ms
- reflectAsm反射優(yōu)化(緩存Method) 100000000 times using 43 ms
- 這其中差的7ms吗货,是reflectAsm生成一次Class文件的損耗泳唠。
下面是反射優(yōu)化的測試樣例:
//通過高性能的ReflectAsm庫進(jìn)行測試,僅進(jìn)行一次methodAccess獲取
@Test
public void reflectAsmGetOnly() {
MethodAccess methodAccess = MethodAccess.get(SimpleBean.class);
Stopwatch watch = Stopwatch.createStarted();
for (long i = 0; i < times; i++) {
methodAccess.invoke(bean, "getName");
}
watch.stop();
String result = String.format(formatter, "reflectAsm反射優(yōu)化+緩存Method", times, watch.elapsed(TimeUnit.MILLISECONDS));
System.out.println(result);
}