為什麼反射慢?

北冥有鱼要继续奋斗發表於2024-07-19

反射機制就是透過位元組碼檔案物件獲取成員變數、成員方法和構造方法,然後進一步獲取它們的具體資訊,如名字、修飾符、型別等。

反射機制的效能較低有很多原因,這裡詳細總結以下4點原因:

(1)JIT最佳化受限:
JIT 編譯器的最佳化是基於靜態分析和預測的。反射是一種在執行時動態解析型別資訊的機制,在編譯時無法確定反射呼叫的具體方法,因此編譯器無法對這些程式碼進行靜態分析,從而無法進行一些JIT最佳化,比如:

內聯最佳化受限:JIT 編譯器通常會對頻繁呼叫的方法進行內聯最佳化,將方法呼叫替換為直接的程式碼。但是,由於反射呼叫的方法在執行時才能確定,因此 JIT 編譯器無法進行有效的內聯最佳化。

無法進行即時編譯:因為反射呼叫的方法在執行時才能確定,因此在解釋執行階段,我們無法確定反射呼叫的方法會被執行多少次,會不會成為熱點程式碼,也就無法對其進行即時編譯最佳化。

(2)反射中頻繁的自動拆裝箱操作會導致應用效能下降:
在反射中,當你呼叫一個方法時,由於在編譯時不知道具體要呼叫的方法引數型別,因此需要用最通用的引用型別來處理所有的引數,即Object。例如,透過Method物件呼叫方法時,使用的invoke方法簽名大致如下:

public Object invoke(Object obj, Object... args)

對於基本資料型別的引數,它們必須被裝箱成對應的包裝類(如IntegerDouble等),以便它們可以作為物件被傳遞。在方法實際執行時,如果方法的引數是基本型別,JVM需要基本型別的值,而不是它們的包裝類物件。因此,JVM會自動進行拆箱。例如,如果你透過反射呼叫的方法期望得到一個int型別的引數,但你傳入的是Integer,在呼叫過程中JVM會自動將Integer物件拆箱為int型別。裝箱和拆箱操作涉及到額外的物件建立(裝箱時)和物件值的提取(拆箱時),在高效能要求的場景下,過度的裝箱和拆箱可能會導致效能瓶頸。此外,由於裝箱操作導致建立了許多短生命週期的物件,這些物件在成為垃圾後,需要透過垃圾回收過程來回收記憶體資源,當有大量物件需要回收時,GC會佔用更多的CPU資源,可能導致應用效能暫時下降。

(3)遍歷操作
反射在呼叫方法時會從方法陣列中遍歷查詢,這對普通的方法呼叫來說是不需要的。

(4)方法訪問檢查
每次使用反射呼叫方法時,JVM都要檢查是否允許訪問該方法,例如是否為私有方法等。這些訪問檢查對普通的方法呼叫來說是不需要的,因為這些檢查都是在編譯時完成的。

相關文章