Java反射在JVM的实现
本文目錄
1. 什么是Java反射,有什么用?
反射使程序代碼能夠接入裝載到JVM中的類的內部信息,允許在編寫與執行時,而不是源代碼中選定的類協作的代碼,是以開發效率換運行效率的一種手段。這使反射成為構建靈活應用的主要工具。
反射可以:
2. Java Class文件的結構
在*.class文件中,以Byte流的形式進行Class的存儲,通過一系列Load,Parse后,Java代碼實際上可以映射為下圖的結構體,這里可以用javap命令或者IDE插件進行查看。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; |
- 常量池(constant pool):類似于C中的DATA段與BSS段,提供常量、字符串、方法名等值或者符號(可以看作偏移定值的指針)的存放
- access_flags: 對Class的flag修飾
1 2 3 4 5 6 7 typedef enum { ??????ACC_PUBLIC = 0x0001, ??????ACC_FINAL = 0x0010, ??????ACC_SUPER = 0x0020, ??????ACC_INTERFACE = 0x0200, ??????ACC_ACSTRACT = 0x0400 ??}AccessFlag - this class/super class/interface: 一個長度為u2的指針,指向常量池中真正的地址,將在Link階段進行符號解引。
- filed: 字段信息,結構體如下
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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等索引,并指向常量池:
它的結構體如下,詳細在這里
| 1 2 3 4 5 6 7 8 9 | 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]; ?} |
以上具體內容可以參考
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()>的靜態代碼段與靜態變量(取決于源碼順序)。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 ??} } |
具體參考如下:
- When and how a Java class is loaded and initialized?
- The Lifetime of a Type
在完成初始化后,就是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主要實現步驟如下
主要慢在如下方面
4.3. Method.invoke的實現
以下為無同步、無異常的情況下調用的步驟
主要慢在如下方面
4.4. class.newInstance的實現
主要慢在如下方面
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
參考這里
ClassLoader0:native的classloader,在JVM中用C寫的,用于加載rt.jar的包,在Java中為空引用。
ExtClassLoader: 用于加載JDK中額外的包,一般不怎么用
AppClassLoader: 加載自己寫的或者引用的第三方包,這個最常見
例子如下
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //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
| 1 2 3 4 5 6 7 | 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,實現加密、壓縮、熱部署等功能,這個是大坑,晚點再開。
5.3. 反射是否慢?
在Stackoverflow上認為反射比較慢的程序員主要有如下看法
當然,現代JVM也不是非常慢了,它能夠對反射代碼進行緩存以及通過方法計數器同樣實現JIT優化,所以反射不一定慢。
更重要的是,很多情況下,你自己的代碼才是限制程序的瓶頸。因此,在開發效率遠大于運行效率的的基礎上,大膽使用反射,放心開發吧。
參考文獻
from:?http://www.importnew.com/21211.html
總結
以上是生活随笔為你收集整理的Java反射在JVM的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员如何用Java排序
- 下一篇: Class对象和Java反射机制