1. 什么是Java反射,有什么用?反射使程序代碼能夠接入裝載到JVM中的類的內部信息,允許在編寫與執行時,而不是源代碼中選定的類協作的代碼,是以開發效率換運行效率的一種手段。這使反射成為構建靈活應用的主要工具。反射可以:調用一些私有方法,實現黑科技。比如雙卡短信發送、設置狀態欄顏色、自動掛電話等。實現序列化與反序列化,比如PO的ORM,Json解析等。實現跨平台兼容,比如JDK中的SocketImpl的實現通過xml或注解,實現依賴注入(DI),注解處理,動態代理,單元測試等功能。比如Retrofit、Spring或者Dagger2. Java Class文件的結構在*.class文件中,以Byte流的形式進行Class的存儲,通過一系列Load,Parse后,Java代碼實際上可以映射為下圖的結構體,這里可以用javap命令或者IDE插件進行查看。——常量池(constant pool):類似於C中的DATA段與BSS段,提供常量、字符串、方法名等值或者符號(可以看作偏移定值的指針)的存放——access_flags: 對Class的flag修飾
——this class/super class/interface: 一個長度為u2的指針,指向常量池中真正的地址,將在Link階段進行符號解引。——filed: 字段信息,結構體如下
method: 提供descriptor, access_flags, Code等索引,並指向常量池:它的結構體如下,詳細在這里https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6
以上具體內容可以參考——JVM文檔https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html——周志明的《深入理解Java虛擬機》,少見的國內精品書籍——一些國外教程的解析http://viralpatel.net/blogs/tutorial-java-class-file-format-revealed/3. Java Class加載的過程Class的加載主要分為兩步第一步通過ClassLoader進行讀取、連結操作第二步進行Class的<clinit>()初始化。3.1. Classloader加載過程ClassLoader 用於加載、連接、緩存Class,可以通過純Java或者native進行實現。在JVM的native代碼中,ClassLoader內部維護着一個線 程安全的HashTable<String,Class>,用於實現對Class字節流解碼后的緩存,如果HashTable中已經有了緩 存,則直接返回緩存;反之,在獲得類名后,通過讀取文件、網絡上的class字節流反序列化為JVM中native的C結構體,接着malloc內存,並 將指針緩存在HashTable中。下面是非數組情況下ClassLoader的流程find/load: 將文件反序列化為C結構體。
Class反序列化的流程link: 根據Class結構體常量池進行符號的解引。比如對象計算內存空間,創建方法表,native invoker,接口方法表,finalizer函數等工作。3.2. 初始化過程當ClassLoader加載Class結束后,將進行Class的初始化操作。主要執行<clinit()>的靜態代碼段與靜態變量(取決於源碼順序)。
具體參考如下:——http://www.programcreek.com/2013/01/when-and-how-a-java-class-is-loaded-and-initialized/——http://www.artima.com/insidejvm/ed2/lifetype.html
在完成初始化后,就是Object的構造<init>了,本文暫不討論。4. 反射在native的實現反射在Java中可以直接調用,不過最終調用的仍是native方法,以下為主流反射操作的實現。4.1. Class.forName的實現Class.forName可以通過包名尋找Class對象,比如Class.forName("java.lang.String")。
在JDK的源碼實現中,可以發現最終調用的是native方法forName0(),它在JVM中調用的實際是findClassFromClassLoader(),原理與ClassLoader的流程一樣,具體實現已經在上面介紹過了。4.2. getDeclaredFields的實現在JDK源碼中,可以知道class.getDeclaredFields()方法實際調用的是native方法getDeclaredFields0(),它在JVM主要實現步驟如下根據Class結構體信息,獲取field_count與fields[]字段,這個字段早已在load過程中被放入了根據field_count的大小分配內存、創建數組將數組進行forEach循環,通過fields[]中的信息依次創建Object對象返回數組指針4.3. Method.invoke的實現以下為無同步、無異常的情況下調用的步驟創建Frame如果對象flag為native,交給native_handler進行處理在frame中執行java代碼彈出Frame返回執行結果的指針
4.4. class.newInstance的實現檢測權限、預分配空間大小等參數創建Object對象,並分配空間通過Method.invoke調用構造函數(<init>())返回Object指針
5. 附錄5.1. JVM與源碼閱讀工具的選擇初 次學習JVM時,不建議去看Android Art、Hotspot等重量級JVM的實現,它內部的防御代碼很多,還有android與libcore、bionic庫緊密耦合,以及分層、內聯甚至 能把編譯器的語義分析繞進去,因此找一個教學用的、嵌入式小型的JVM有利於節約自己的時間。因為以前折騰過OpenWrt,聽過有大神推薦過 jamvm,只有不到200個源文件,非常適合學習。在工具的選擇上,個人推薦SourceInsight。對比了好幾個工具clion,vscode,sublime,sourceinsight,只有sourceinsight對索引、符號表的解析最准確。5.2. 關於幾個ClassLoader參考http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader。ClassLoader0:native的classloader,在JVM中用C寫的,用於加載rt.jar的包,在Java中為空引用。ExtClassLoader: 用於加載JDK中額外的包,一般不怎么用AppClassLoader: 加載自己寫的或者引用的第三方包,這個最常見例子如下
最后就是getContextClassLoader(),它在Tomcat中使用,通過設置一個臨時變量,可以向子類ClassLoader去加載,而不是委托給ParentClassLoader
最后還有一些自定義的ClassLoader,實現加密、壓縮、熱部署等功能,這個是大坑,晚點再開。5.3. 反射是否慢?在Stackoverflow上認為反射比較慢的程序員主要有如下看法驗證等防御代碼過於繁瑣,這一步本來在link階段,現在卻在計算時進行驗證產生很多臨時對象,造成GC與計算時間消耗由於缺少上下文,丟失了很多運行時的優化,比如JIT(它可以看作JVM的重要評測標准之一)當然,現代JVM也不是非常慢了,它能夠對反射代碼進行緩存以及通過方法計數器同樣實現JIT優化,所以反射不一定慢。更重要的是,很多情況下,你自己的代碼才是限制程序的瓶頸。因此,在開發效率遠大於運行效率的的基礎上,大膽使用反射,放心開發吧。