本文目錄 什么是Java反射,有什么用? Java Class文件的結構 Java Class加載的過程 反射在native的實現(xiàn) 附錄 1、什么是Java反射,有什么用? 反射使程序代碼能夠接入裝載到JVM中的類的內(nèi)部信息,允許在編寫與執(zhí)行時,而不是源代碼中選定的類協(xié)作的代碼,是以開發(fā)效率換運行效率的一種手段。這使反射成為構建靈活應用的主要工具。 反射可以: 1、調(diào)用一些私有方法,實現(xiàn)黑科技。比如雙卡短信發(fā)送、設置狀態(tài)欄顏色、自動掛電話等。 2、實現(xiàn)序列化與反序列化,比如PO的ORM,Json解析等。 3、實現(xiàn)跨平臺兼容,比如JDK中的SocketImpl的實現(xiàn) 4、通過xml或注解,實現(xiàn)依賴注入(DI),注解處理,動態(tài)代理,單元測試等功能。比如Retrofit、Spring或者Dagger 2、Java Class文件的結構 在*.class文件中,以Byte流的形式進行Class的存儲,通過一系列Load,Parse后,Java代碼實際上可以映射為下圖的結構體,這里可以用javap命令或者IDE插件進行查看。 typedef struct { u4 magic;/*0xCAFEBABE*/ u2 minor_version; /*網(wǎng)上有表可查*/ u2 major_version; /*網(wǎng)上有表可查*/ 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;
3. Java Class加載的過程 Class的加載主要分為兩步
3.1. Classloader加載過程 ClassLoader用于加載、連接、緩存Class,可以通過純Java或者native進行實現(xiàn)。在JVM的native代碼中,ClassLoader內(nèi)部維護著一個線程安全的HashTable<String,Class>,用于實現(xiàn)對Class字節(jié)流解碼后的緩存,如果HashTable中已經(jīng)有了緩存,則直接返回緩存;反之,在獲得類名后,通過讀取文件、網(wǎng)絡上的class字節(jié)流反序列化為JVM中native的C結構體,接著malloc內(nèi)存,并將指針緩存在HashTable中。 下面是非數(shù)組情況下ClassLoader的流程
Class反序列化的流程
3.2. 初始化過程 當ClassLoader加載Class結束后,將進行Class的初始化操作。主要執(zhí)行<clinit()>的靜態(tài)代碼段與靜態(tài)變量(取決于源碼順序)。 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>了,本文暫不討論。 4. 反射在native的實現(xiàn) 反射在Java中可以直接調(diào)用,不過最終調(diào)用的仍是native方法,以下為主流反射操作的實現(xiàn)。 4.1. Class.forName的實現(xiàn) Class.forName可以通過包名尋找Class對象,比如Class.forName("java.lang.String")。 在JDK的源碼實現(xiàn)中,可以發(fā)現(xiàn)最終調(diào)用的是native方法forName0(),它在JVM中調(diào)用的實際是findClassFromClassLoader(),原理與ClassLoader的流程一樣,具體實現(xiàn)已經(jīng)在上面介紹過了。 4.2. getDeclaredFields的實現(xiàn) 在JDK源碼中,可以知道class.getDeclaredFields()方法實際調(diào)用的是native方法getDeclaredFields0(),它在JVM主要實現(xiàn)步驟如下
4.3. Method.invoke的實現(xiàn) 以下為無同步、無異常的情況下調(diào)用的步驟
4.4. class.newInstance的實現(xiàn)
5. 附錄 5.1. JVM與源碼閱讀工具的選擇 初次學習JVM時,不建議去看Android Art、Hotspot等重量級JVM的實現(xiàn),它內(nèi)部的防御代碼很多,還有android與libcore、bionic庫緊密耦合,以及分層、內(nèi)聯(lián)甚至能把編譯器的語義分析繞進去,因此找一個教學用的、嵌入式小型的JVM有利于節(jié)約自己的時間。因為以前折騰過OpenWrt,聽過有大神推薦過jamvm,只有不到200個源文件,非常適合學習。 在工具的選擇上,個人推薦SourceInsight。對比了好幾個工具clion,vscode,sublime,sourceinsight,只有sourceinsight對索引、符號表的解析最準確。 5.2. 關于幾個ClassLoader 參考這里 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,實現(xiàn)加密、壓縮、熱部署等功能,這個是大坑,晚點再開。 5.3. 反射是否慢? 在Stackoverflow上認為反射比較慢的程序員主要有如下看法
當然,現(xiàn)代JVM也不是非常慢了,它能夠?qū)Ψ瓷浯a進行緩存以及通過方法計數(shù)器同樣實現(xiàn)JIT優(yōu)化,所以反射不一定慢。 更重要的是,很多情況下,你自己的代碼才是限制程序的瓶頸。因此,在開發(fā)效率遠大于運行效率的的基礎上,大膽使用反射,放心開發(fā)吧。
|
|