Java反射在JVM的實現

1. 什麼是Java反射,有什麼用?

反射使程序代碼可以接入裝載到JVM中的類的內部信息,容許在編寫與執行時,而不是源代碼中選定的類協做的代碼,是以開發效率換運行效率的一種手段。這使反射成爲構建靈活應用的主要工具。html

反射能夠:java

  1. 調用一些私有方法,實現黑科技。好比雙卡短信發送、設置狀態欄顏色、自動掛電話等。
  2. 實現序列化與反序列化,好比PO的ORM,Json解析等。
  3. 實現跨平臺兼容,好比JDK中的SocketImpl的實現
  4. 經過xml或註解,實現依賴注入(DI),註解處理,動態代理,單元測試等功能。好比Retrofit、Spring或者Dagger

2. Java Class文件的結構

在*.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;
  • 常量池(constant pool):相似於C中的DATA段與BSS段,提供常量、字符串、方法名等值或者符號(能夠看做偏移定值的指針)的存放
  • access_flags: 對Class的flag修飾
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: 字段信息,結構體以下
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];
  }

以上具體內容能夠參考緩存

  1. JVM文檔
  2. 周志明的《深刻理解Java虛擬機》,少見的國內精品書籍
  3. 一些國外教程的解析

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的流程oracle

  • find/load: 將文件反序列化爲C結構體。

Class反序列化的流程app

  • link: 根據Class結構體常量池進行符號的解引。好比對象計算內存空間,建立方法表,native invoker,接口方法表,finalizer函數等工做。

3.2. 初始化過程

當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>了,本文暫不討論。

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主要實現步驟以下

  1. 根據Class結構體信息,獲取field_countfields[]字段,這個字段早已在load過程當中被放入了
  2. 根據field_count的大小分配內存、建立數組
  3. 將數組進行forEach循環,經過fields[]中的信息依次建立Object對象
  4. 返回數組指針

主要慢在以下方面

  1. 建立、計算、分配數組對象
  2. 對字段進行循環賦值

4.3. Method.invoke的實現

如下爲無同步、無異常的狀況下調用的步驟

  1. 建立Frame
  2. 若是對象flag爲native,交給native_handler進行處理
  3. 在frame中執行java代碼
  4. 彈出Frame
  5. 返回執行結果的指針

主要慢在以下方面

  1. 須要徹底執行ByteCode而缺乏JIT等優化
  2. 檢查參數很是多,這些原本能夠在編譯器或者加載時完成

4.4. class.newInstance的實現

  1. 檢測權限、預分配空間大小等參數
  2. 建立Object對象,並分配空間
  3. 經過Method.invoke調用構造函數(<init>())
  4. 返回Object指針

主要慢在以下方面

  1. 參數檢查不能優化或者遺漏
  2. <init>()的查表
  3. Method.invoke自己耗時

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: 加載本身寫的或者引用的第三方包,這個最多見

例子以下

//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,實現加密、壓縮、熱部署等功能,這個是大坑,晚點再開。

5.3. 反射是否慢?

在Stackoverflow上認爲反射比較慢的程序員主要有以下見解

  1. 驗證等防護代碼過於繁瑣,這一步原本在link階段,如今卻在計算時進行驗證
  2. 產生不少臨時對象,形成GC與計算時間消耗
  3. 因爲缺乏上下文,丟失了不少運行時的優化,好比JIT(它能夠看做JVM的重要評測標準之一)

固然,現代JVM也不是很是慢了,它可以對反射代碼進行緩存以及經過方法計數器一樣實現JIT優化,因此反射不必定慢。

更重要的是,不少狀況下,你本身的代碼纔是限制程序的瓶頸。所以,在開發效率遠大於運行效率的的基礎上,大膽使用反射,放心開發吧。

參考文獻

  1. http://www.codeceo.com/article/reflect-bad.html
  2. http://blog.csdn.net/lmj623565791/article/details/43452969
  3. http://codekk.com/open-source-project-analysis/detail/Android/Trinea/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8BJava%20%E6%B3%A8%E8%A7%A3%20Annotation
  4. http://www.trinea.cn/android/java-annotation-android-open-source-analysis/
  5. http://www.codeceo.com/article/java-jvm.html
相關文章
相關標籤/搜索