反射技巧讓你的效能提升N倍
Hi 大家好,我是 DHL。公眾號:ByteCode ,專注分享有趣硬核原創內容,Kotlin、Jetpack、效能優化、系統原始碼、演算法及資料結構、動畫、大廠面經
在之前的文章和影片中我們拆分了不同的場景對比反射的效能。
- 文字版: 側重於細節上的知識點更多、更加詳細,揭祕反射真的很耗時嗎,射 10 萬次耗時多久
- 影片版: 通過動畫展示講解,更加的清楚、直觀,影片版本 bilibili 地址: b23.tv/Hprua24
在之前的文章中提到了一個提升效能非常重要的點,將 Accessible
設定 true
反射速度會進一步提升,如果單看一個程式,可能這點效能微不足道,但是如果放在一個大的複雜的工程下面,執行在大量的低端機下,一行程式碼提升的效能,可能比你寫 100 行程式碼提升的效能更加顯著。
而今天這篇文章從原始碼的角度分析一下 isAccessible()
方法的作用,為什麼將 Accessible
設定為 true
可以提升效能,在開始分析之前,我們先寫一段程式碼。
- 宣告一個普通類,裡面有個
public
方法getName()
和private
方法getAddress()
``` class Person { public fun getName(): String { return "I am DHL" }
private fun getAddress(): String {
return "BJ"
}
} ```
- 通過反射獲取
getName()
和getAddress()
方法,花 3 秒鐘思考一下,下面的程式碼輸出的結果
``` // public 方法 val method1 = Person::class.declaredFunctions.find { it.name == "getName" } println("access = ${method1?.isAccessible}")
// private 方法 val method2 = Person::class.declaredFunctions.find { it.name == "getAddress" } println("access = ${method2?.isAccessible}") ```
無論是呼叫 public getName()
方法還是呼叫 private getAddress()
方法,最後輸出的結果都為 false
,通過這個例子也間接說明了 isAccessible()
方法並不是用來表示訪問許可權的。
當我們通過反射呼叫 private
方法時,都需要執行 setAccessible()
方法設定為 true
, 否者會丟擲下面的異常。
java.lang.IllegalAccessException: can not access a member of class com.hi.dhl.demo.reflect.Person
如果通過反射呼叫 public
方法,不設定 Accessible
為 true
,也可以正常呼叫,所以有很多小夥伴認為 isAccessible()
方法用來表示訪問許可權,其實這種理解是錯誤的。
我們一起來看一下原始碼是如何解釋的,方法 isAccessible()
位於 AccessibleObject
類中。
``` public class AccessibleObject implements AnnotatedElement { ...... // NOTE: for security purposes, this field must not be visible boolean override;
public boolean isAccessible() {
return override;
}
public void setAccessible(boolean flag) throws SecurityException {
......
}
......
} ```
AccessibleObject
是 Field
、 Method
、 Constructor
的父類,呼叫 isAccessible()
返回 override
的值,而欄位 override
主要判斷是否要進行安全檢查。
欄位 override
在 AccessibleObject
子類當中使用,所以我們一起來看一下它的子類 Method
。
public Object invoke(Object obj, Object... args){
// 是否要進行安全檢查
if (!override) {
// 進行快速驗證是否是 Public 方法
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
// 返回呼叫這個方法的 Class
Class<?> caller = Reflection.getCallerClass();
// 做許可權訪問的校驗,快取呼叫這個方法的 Class,避免下次在做檢查
checkAccess(caller, clazz, obj, modifiers);
}
}
......
return ma.invoke(obj, args);
}
欄位 override
提供給子類去重寫,它的值決定了是否要進行安全檢查,如果要進行安全檢查,則會執行 quickCheckMemberAccess()
快速驗證是否是 Public
方法,避免呼叫 getCallerClass()
。
- 如果是
Public
方法,避免做安全檢查,所以我們在程式碼中不呼叫setAccessible(true)
方法,也不會丟擲異常 - 如果不是
Public
方法則會呼叫getCallerClass()
獲取呼叫這個方法的 Class,執行checkAccess()
方法進行安全檢查。
``` // it is necessary to perform somewhat expensive security checks. // A more complicated security check cache is needed for Method and Field // The cache can be either null (empty cache) volatile Object securityCheckCache; // 快取呼叫這個方法的 Class
void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers){ ...... Object cache = securityCheckCache; // read volatile
if(cache == 呼叫這個方法的 Class){
return; // ACCESS IS OK
}
slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
......
}
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,Class<?> targetClass){ Reflection.ensureMemberAccess(caller, clazz, obj, modifiers); Object cache = 呼叫這個方法的 Class securityCheckCache = cache; // 快取呼叫這個方法的 Class } ```
原始碼中註釋也說明了,如果要進行安全檢查那麼它的代價是非常昂貴的,所以用變數 securityCheckCache
快取呼叫這個方法的 Class。如果下次使用相同的 Class,就不需要在做安全檢查,但是這個快取有個缺陷,如果換一個呼叫這個方法的 Class,需要再次做安全檢查,並且會覆蓋之前的快取結果。
如果要在執行時修改屬性或者呼叫某個方法時,都要進行安全檢查,而安全檢查是非常消耗資源的,所以 JDK 提供了一個 setAccessible()
方法,可以繞過安全檢查,讓開發者自己來決定是否要避開安全檢查。
因為反射本身是非常慢的,如果能夠避免安全檢查,可以進一步提升效能,在之前的文章 揭祕反射真的很耗時嗎,射 10 萬次耗時多久,針對不同場景,分別測試了反射前後以及關閉安全檢查的耗時。
| | 正常呼叫 | 反射 | 反射優化後 | 反射優化後關掉安全檢查 | | --- | --- | --- | --- | --- | | 建立物件 | 0.578 ms/op| 4.710 ms/op | 1.018 ms/op | 0.943 ms/op | | 方法呼叫 | 0.422 ms/op | 10.533 ms/op | 0.844 ms/op | 0.687 ms/op | | 屬性呼叫 | 0.241 ms/op | 12.432 ms/op | 1.362 ms/op | 1.202 ms/op | | 伴生物件 | 0.470 ms/op | 5.661 ms/op | 0.840 ms/op | 0.702 ms/op |
從測試結果可以看出來,執行 setAccessible()
方法,設定為 true
關掉安全檢查之後,反射速度得到了進一步的提升,更接近於正常呼叫。
我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。
全文到這裡就結束了,感謝你的閱讀,堅持原創不易,歡迎在看、點贊、分享給身邊的小夥伴,我會持續分享原創乾貨!!!
真誠推薦你關注我,公眾號:ByteCode ,持續分享硬核原創內容,Kotlin、Jetpack、效能優化、系統原始碼、演算法及資料結構、動畫、大廠面經。
近期必讀熱門文章
- 90%人不懂的泛型侷限性,泛型擦除,星投影
- 90%的人都不知道的知識點,Kotlin 和 Java 的協變和逆變
- CPU 如何記錄函式呼叫過程和返回過程
- 揭祕反射真的很耗時嗎,射 10 萬次耗時多久
- 你知道 Iterable 有多慢嗎?試試它提升效能
- 揭祕 Kotlin 1.6.20 重磅功能 Context Receivers
- Stack Overflow 上最熱門的 10 個 Kotlin 問題
- Android 12 已來,你的 App 崩潰了嗎?
- Google 宣佈廢棄 LiveData.observe 方法
- 影響效能的 Kotlin 程式碼(一)
- 揭祕 Kotlin 中的 == 和 ===
最後推薦長期更新和維護的專案
-
個人部落格,將所有文章進行分類,歡迎前去檢視 https://hi-dhl.com
-
KtKit 小巧而實用,用 Kotlin 語言編寫的工具庫,歡迎前去檢視 KtKit
-
計劃建立一個最全、最新的 AndroidX Jetpack 相關元件的實戰專案以及相關元件原理分析文章,正在逐漸增加 Jetpack 新成員,倉庫持續更新,歡迎前去檢視 AndroidX-Jetpack-Practice
-
LeetCode / 劍指 offer / 國內外大廠面試題 / 多執行緒題解,語言 Java 和 kotlin,包含多種解法、解題思路、時間複雜度、空間複雜度分析