簡書 占小狼
轉載請注明原創出處,謝謝!
“物有本末,事有始終。知其先后,則近道矣”
前段時間看了笨神的 從一起GC血案談到反射原理一本,就把Java方法的反射機制實現擼了一遍。
方法反射實例
public class ReflectCase {
public static void main(String[] args) throws Exception {
Proxy target = new Proxy();
Method method = Proxy.class.getDeclaredMethod("run");
method.invoke(target);
}
static class Proxy {
public void run() {
System.out.println("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
類加載器對象
這里每次都生成新的類加載器,是為了性能考慮,在某些情況下可以卸載這些生成的類,因為類的卸載是只有在類加載器可以被回收的情況下才會被回收的,如果用了原來的類加載器,那可能導致這些新創建的類一直無法被卸載,從其設計來看本身就不希望這些類一直存在內存里的,在需要的時候有就行了。
我是占小狼,如果讀完覺得有收獲的話,歡迎點贊加關注