java 反射 asm_Java 下高效的反射工具包 ReflectASM 使用例解

ReflectASM 使用字節碼生成的方式實現了更為高效的反射機制。執行時會生成一個存取類來 set/get 字段,訪問方法或創建實例。一看到 ASM 就能領悟到 ReflectASM 會用字節碼生成的方式,而不是依賴於 Java 本身的反射機制來實現的,所以它更快,並且避免了訪問原始類型因自動裝箱而產生的問題。

下面三個圖是 ReflectASM 與 Java 自身反射機制的性能對比,表現很不錯的。

aHR0cDovL2NoYXJ0LmFwaXMuZ29vZ2xlLmNvbS9jaGFydD9jaG1hPTEwMCZjaHR0PUZpZWxkJTIwU2V0L0dldCZjaHM9NzAweDYyJmNoZD10OjE0MDIwODEsMTEzMzkxMDcmY2hkcz0wLDExMzM5MTA3JmNoeGw9MDolN0NKYXZhJTIwUmVmbGVjdGlvbiU3Q0ZpZWxkQWNjZXNzJmNodD1iaGcmY2hiaD0xMCZjaHh0PXkmY2hjbz02NjAwRkYmLnBuZw==

aHR0cDovL2NoYXJ0LmFwaXMuZ29vZ2xlLmNvbS9jaGFydD9jaG1hPTEwMCZjaHR0PU1ldGhvZCUyMENhbGwmY2hzPTcwMHg2MiZjaGQ9dDo5NzM5MCwyMDg3NTAmY2hkcz0wLDIwODc1MCZjaHhsPTA6JTdDSmF2YSUyMFJlZmxlY3Rpb24lN0NNZXRob2RBY2Nlc3MmY2h0PWJoZyZjaGJoPTEwJmNoeHQ9eSZjaGNvPTY2MDBBQSYucG5n

aHR0cDovL2NoYXJ0LmFwaXMuZ29vZ2xlLmNvbS9jaGFydD9jaG1hPTEwMCZjaHR0PUNvbnN0cnVjdG9yJmNocz03MDB4NjImY2hkPXQ6Mjg1MzA2Myw1ODI4OTkzJmNoZHM9MCw1ODI4OTkzJmNoeGw9MDolN0NKYXZhJTIwUmVmbGVjdGlvbiU3Q0NvbnN0cnVjdG9yQWNjZXNzJmNodD1iaGcmY2hiaD0xMCZjaHh0PXkmY2hjbz02NjAwNjYmLnBuZw==

測試代碼包含在項目文件中. 上面圖形是在  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() 的調用棧:

aHR0cDovL3VubWkuY2Mvd3AtY29udGVudC91cGxvYWRzLzIwMTIvMDgvdGVzdEpka1JlZmxlY3RfZGVidWcucG5n

借助了 JDK 的 DelegatingMethodAccessorImpl 和  NativeMethodAccessorImpl。

再看 testReflectAsm() 到 SomeClass.foo()的調用棧:

aHR0cDovL3VubWkuY2Mvd3AtY29udGVudC91cGxvYWRzLzIwMTIvMDgvdGVzdFJlZmxlY3RBc21fZGVidWcucG5n

可以看到,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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值