ReflectASM 使用字節碼生成的方式實現了更為高效的反射機制。執行時會生成一個存取類來 set/get 字段,訪問方法或創建實例。一看到 ASM 就能領悟到 ReflectASM 會用字節碼生成的方式,而不是依賴於 Java 本身的反射機制來實現的,所以它更快,並且避免了訪問原始類型因自動裝箱而產生的問題。
下面三個圖是 ReflectASM 與 Java 自身反射機制的性能對比,表現很不錯的。
測試代碼包含在項目文件中. 上面圖形是在 Oracle 的 Java 7u3, server VM 下測試出的結果。
下面我們自己來做個測試,測試環境是 Mac OS X 10.8, 2.4G Core 2 Duo, 4G RAM, 64 位 JDK 1.6.
待反射的類 SomeClass.java
測試類 ReflectasmClient.java
分別運行 testJdkReflect() 和 testReflectAsm 得出各自的運行時間數據,如下:
運行 testJdkReflect(): 31473 31663 31578 31658 31552
運行 testReflectAsm(): 312814 310666 312867 311234 311792
這個數據是非常恐怖的,似乎在帶領我們往相反的方向上走,用 ReflectASM 怎么反而耗時多的多,高一個數量級,為什么呢?原因是大部分的時間都耗費在了
MethodAccess access = MethodAccess.get(SomeClass.class);
上,正是生成字節碼的環節上,也讓你體驗到 MethodAccess 是個無比耗時的操作,如果把這行放到循環之外會是什么樣的結果呢,同時也把方法 testJdkReflect() 中的
Method method = SomeClass.class.getMethod("foo", String.class);
也提出去,改變后的 testJdkReflect() 和 testReflectAsm() 分別如下:
再次分別跑下 testJdkReflect() 和 testReflectAsm(),新的結果如下:
運行 testJdkReflect(): 1682 1696 1858 1774 1780 ------ 平均 1758
運行 testReflectAsm(): 327 549 520 509 514 ------ 平均 483.8
勝負十分明顯,上面的實驗兩相一比較,用 ReflectAsm 進行方法調用節省時間是 72.48%
也因此可以得到使用 ReflectASM 時需特別注意的是,獲得類似 MethodAccess 實例只做一次,或它的實例應緩存起來,才是真正用好 ReflectASM。
進一步深入的話,不妨看看分別從 testJdkReflect()/testReflectAsm() 到 SomeClass.foo() 過程中到底發生了什么,斷點看調用棧。
testJdkReflect() 到 SomeClass.foo() 的調用棧:
借助了 JDK 的 DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。
再看 testReflectAsm() 到 SomeClass.foo()的調用棧:
可以看到,ReflectAsm 在執行 MethodAccess access = MethodAccess.get(SomeClass.class); 為你生成了類 SomeClassMethodAccess,經由它來進行后續的方法調用,使得性能上有很可觀的改善。
上面只是講述了,調用方法時如何使用 ReflectAsm,以及怎么確保高效性。下面補上 ReflectAsm 更多的用法,翻譯自 ReflectAsm 官方。
ReflectASM 反射調用方法:
用 ReflectASM 反射來 set/get 字段值:
用 ReflectASM 反射來調用構造方法:
避免用方法名來查找
為了在重復性的反射來訪問方法或字段時最大化性能,應該用方法和字段的索引來定位而不是名稱:
說到這,不妨再次來驗證一下,把 testReflectAsm() 方法改為如下:
運行的輸出結果是,你可能想像不到的:
206 182 171 175 171
而用名稱查找方法時的測試數據為:327 549 520 509 514
當然你調用的重復性應該帶有一點誇張性質的。性能更優化的原因是用名稱來查找最科要被轉換成索引來查找。
可見性
ReflectASM 總是能訪問公有成員的. 它會嘗試在同一個 package 中去定義訪問類的,並且同一個類加載器去加載。所以,如果安全管理器允許 setAccessible 調用成功的話,protected 或包私有(package private) 的成員也可被訪問到. 假如 setAccessible 失敗,僅當當有公有成員可被訪問時,不會有異常拋出. 私有成員總是無法訪問到。
有關異常
當使用 ReflectASM 有異常時,棧跟蹤更清淅了。這是 Java 在反射調用方法時拋出了一個 RuntimeException 異常:
再看用 ReflectASM 時拋出的同樣的異常:
如果被 ReflectASM 調用的代碼拋出了需檢測的異常,也需要拋出需檢測異常. 因為如果你在用 try/catch 捕獲塊中未聲明拋出的具體類型的異常時會報編譯錯誤。(Unmi 注:這句話的意思是說,比如方法 foo() 未聲明拋出 IOException,而你 try 它時卻 catch(IOException) 就會出現編譯錯誤)所以當你在用 ReflectASM 反射調用,並需要關心其中拋出的異常時,你必須捕獲的異常類型是 Exception。