深入分析Java方法反射的實現原理
前段時間看了笨神的 從一起GC血案談到反射原理一本,就把Java方法的反射機制實現擼了一遍。
方法反射例項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class .getDeclaredMethod( "run" );
|
通過Java的反射機制,可以在執行期間呼叫物件的任何方法,如果大量使用這種方式進行呼叫,會有效能或記憶體隱患麼?為了徹底瞭解方法的反射機制,只能從底層程式碼入手了。
Method獲取
呼叫Class
類的getDeclaredMethod
可以獲取指定方法名和引數的方法物件Method
getDeclaredMethod
其中privateGetDeclaredMethods
方法從快取或JVM中獲取該Class
中申明的方法列表,searchMethods
方法將從返回的方法列表裡找到一個匹配名稱和引數的方法物件。
searchMethods
如果找到一個匹配的Method
,則重新copy一份返回,即Method.copy()
方法
所次每次呼叫getDeclaredMethod
方法返回的Method
物件其實都是一個新的物件,且新物件的root
屬性都指向原來的Method
物件,如果需要頻繁呼叫,最好把Method
物件快取起來。
privateGetDeclaredMethods
從快取或JVM中獲取該Class
中申明的方法列表,實現如下:
其中reflectionData()
方法實現如下:
這裡有個比較重要的資料結構ReflectionData
,用來快取從JVM中讀取類的如下屬性資料:
從reflectionData()
方法實現可以看出:reflectionData
物件是SoftReference
型別的,說明在記憶體緊張時可能會被回收,不過也可以通過-XX:SoftRefLRUPolicyMSPerMB
引數控制回收的時機,只要發生GC就會將其回收,如果reflectionData
被回收之後,又執行了反射方法,那隻能通過newReflectionData
方法重新建立一個這樣的物件了,newReflectionData
方法實現如下:
通過unsafe.compareAndSwapObject
方法重新設定reflectionData
欄位;
在privateGetDeclaredMethods
方法中,如果通過reflectionData()
獲得的ReflectionData
物件不為空,則嘗試從ReflectionData
物件中獲取declaredMethods
屬性,如果是第一次,或則被GC回收之後,重新初始化後的類屬性為空,則需要重新到JVM中獲取一次,並賦值給ReflectionData
,下次呼叫就可以使用快取資料了。
Method呼叫
獲取到指定的方法物件Method
之後,就可以呼叫它的invoke
方法了,invoke
實現如下:
應該注意到:這裡的MethodAccessor
物件是invoke
方法實現的關鍵,一開始methodAccessor
為空,需要呼叫acquireMethodAccessor
生成一個新的MethodAccessor
物件,MethodAccessor
本身就是一個介面,實現如下:
在acquireMethodAccessor
方法中,會通過ReflectionFactory
類的newMethodAccessor
建立一個實現了MethodAccessor
介面的物件,實現如下:
在ReflectionFactory
類中,有2個重要的欄位:noInflation
(預設false
)和inflationThreshold
(預設15),在checkInitted
方法中可以通過-Dsun.reflect.inflationThreshold=xxx
和-Dsun.reflect.noInflation=true
對這兩個欄位重新設定,而且只會設定一次;
如果noInflation
為false
,方法newMethodAccessor
都會返回DelegatingMethodAccessorImpl
物件,DelegatingMethodAccessorImpl
的類實現
其實,DelegatingMethodAccessorImpl
物件就是一個代理物件,負責呼叫被代理物件delegate
的invoke
方法,其中delegate
引數目前是NativeMethodAccessorImpl
物件,所以最終Method
的invoke
方法呼叫的是NativeMethodAccessorImpl
物件invoke
方法,實現如下:
這裡用到了ReflectionFactory
類中的inflationThreshold
,當delegate
呼叫了15次invoke
方法之後,如果繼續呼叫就通過MethodAccessorGenerator
類的generateMethod
方法生成MethodAccessorImpl
物件,並設定為delegate
物件,這樣下次執行Method.invoke
時,就呼叫新建的MethodAccessor
物件的invoke()
方法了。
這裡需要注意的是:generateMethod
方法在生成MethodAccessorImpl
物件時,會在記憶體中生成對應的位元組碼,並呼叫ClassDefiner.defineClass
建立對應的class物件,實現如下:
在ClassDefiner.defineClass
方法實現中,每被呼叫一次都會生成一個DelegatingClassLoader
類載入器物件
這裡每次都生成新的類載入器,是為了效能考慮,在某些情況下可以解除安裝這些生成的類,因為類的解除安裝是隻有在類載入器可以被回收的情況下才會被回收的,如果用了原來的類載入器,那可能導致這些新建立的類一直無法被解除安裝,從其設計來看本身就不希望這些類一直存在記憶體裡的,在需要的時候有就行了。