反射使程序代碼可以接入裝載到JVM中的類的內部信息,容許在編寫與執行時,而不是源代碼中選定的類協做的代碼,是以開發效率換運行效率的一種手段。這使反射成爲構建靈活應用的主要工具。html
反射能夠:java
在*.class文件中,以Byte流的形式進行Class的存儲,經過一系列Load,Parse後,Java代碼實際上能夠映射爲下圖的結構體,這裏能夠用javap
命令或者IDE插件進行查看。android
typedef struct { u4 magic;/*0xCAFEBABE*/ u2 minor_version; /*網上有表可查*/ u2 major_version; /*網上有表可查*/ u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; //重要 u2 fields_count; field_info fields[fields_count]; //重要 u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }ClassBlock;
typedef enum { ACC_PUBLIC = 0x0001, ACC_FINAL = 0x0010, ACC_SUPER = 0x0020, ACC_INTERFACE = 0x0200, ACC_ACSTRACT = 0x0400 }AccessFlag
typedef struct fieldblock { char *name; char *type; char *signature; u2 access_flags; u2 constant; union { union { char data[8]; uintptr_t u; long long l; void *p; int i; } static_value; u4 offset; } u; } FieldBlock;
method: 提供descriptor, access_flags, Code等索引,並指向常量池:程序員
它的結構體以下,詳細在這裏數組
method_info { u2 access_flags; u2 name_index; //the parameters that the method takes and the //value that it return u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
以上具體內容能夠參考緩存
Class的加載主要分爲兩步安全
<clinit>()
初始化。ClassLoader用於加載、鏈接、緩存Class,能夠經過純Java或者native進行實現。在JVM的native代碼中,ClassLoader內部維護着一個線程安全的HashTable<String,Class>
,用於實現對Class字節流解碼後的緩存,若是HashTable中已經有了緩存,則直接返回緩存;反之,在得到類名後,經過讀取文件、網絡上的class字節流反序列化爲JVM中native的C結構體,接着malloc內存,並將指針緩存在HashTable中。網絡
下面是非數組狀況下ClassLoader的流程oracle
Class反序列化的流程app
當ClassLoader加載Class結束後,將進行Class的初始化操做。主要執行<clinit()>
的靜態代碼段與靜態變量(取決於源碼順序)。
public class Sample { //step.1 static int b = 2; //step.2 static { b = 3; } public static void main(String[] args) { Sample s = new Sample(); System.out.println(s.b); //b=3 } }
具體參考以下:
在完成初始化後,就是Object的構造<init>
了,本文暫不討論。
反射在Java中能夠直接調用,不過最終調用的還是native方法,如下爲主流反射操做的實現。
Class.forName能夠經過包名尋找Class對象,好比Class.forName("java.lang.String")
。
在JDK的源碼實現中,能夠發現最終調用的是native方法forName0()
,它在JVM中調用的實際是findClassFromClassLoader()
,原理與ClassLoader的流程同樣,具體實現已經在上面介紹過了。
在JDK源碼中,能夠知道class.getDeclaredFields()
方法實際調用的是native方法getDeclaredFields0()
,它在JVM主要實現步驟以下
field_count
與fields[]
字段,這個字段早已在load過程當中被放入了field_count
的大小分配內存、建立數組fields[]
中的信息依次建立Object對象主要慢在以下方面
- 建立、計算、分配數組對象
- 對字段進行循環賦值
如下爲無同步、無異常的狀況下調用的步驟
主要慢在以下方面
- 須要徹底執行ByteCode而缺乏JIT等優化
- 檢查參數很是多,這些原本能夠在編譯器或者加載時完成
<init>()
)主要慢在以下方面
- 參數檢查不能優化或者遺漏
<init>()
的查表- Method.invoke自己耗時
初次學習JVM時,不建議去看Android Art、Hotspot等重量級JVM的實現,它內部的防護代碼不少,還有android與libcore、bionic庫緊密耦合,以及分層、內聯甚至能把編譯器的語義分析繞進去,所以找一個教學用的、嵌入式小型的JVM有利於節約本身的時間。由於之前折騰過OpenWrt,聽過有大神推薦過jamvm,只有不到200個源文件,很是適合學習。
在工具的選擇上,我的推薦SourceInsight。對比了好幾個工具clion,vscode,sublime,sourceinsight,只有sourceinsight對索引、符號表的解析最準確。
參考這裏
ClassLoader0:native的classloader,在JVM中用C寫的,用於加載rt.jar的包,在Java中爲空引用。
ExtClassLoader: 用於加載JDK中額外的包,通常不怎麼用
AppClassLoader: 加載本身寫的或者引用的第三方包,這個最多見
例子以下
//sun.misc.Launcher$AppClassLoader@4b67cf4d //which class you create or jars from thirdParty //第一個很是有歧義,可是它的確是AppClassLoader ClassLoader.getSystemClassLoader(); com.test.App.getClass().getClassLoader(); Class.forName("ccom.test.App").getClassLoader() //sun.misc.Launcher$ExtClassLoader@66d3c617 //Class loaded in ext jar Class.forName("sun.net.spi.nameservice.dns.DNSNameService") //null, class loaded in rt.jar String.class.getClassLoader() Class.forName("java.lang.String").getClassLoader() Class.forName("java.lang.Class").getClassLoader() Class.forName("apple.launcher.JavaAppLauncher").getClassLoader()
最後就是getContextClassLoader()
,它在Tomcat中使用,經過設置一個臨時變量,能夠向子類ClassLoader去加載,而不是委託給ParentClassLoader
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // call some API that uses reflection without taking ClassLoader param } finally { Thread.currentThread().setContextClassLoader(originalClassLoader); }
最後還有一些自定義的ClassLoader,實現加密、壓縮、熱部署等功能,這個是大坑,晚點再開。
在Stackoverflow上認爲反射比較慢的程序員主要有以下見解
固然,現代JVM也不是很是慢了,它可以對反射代碼進行緩存以及經過方法計數器一樣實現JIT優化,因此反射不必定慢。
更重要的是,不少狀況下,你本身的代碼纔是限制程序的瓶頸。所以,在開發效率遠大於運行效率的的基礎上,大膽使用反射,放心開發吧。