乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Java反射在JVM的實現(xiàn)

       小小郡 2016-09-19

      Java反射在JVM的實現(xiàn)

      本文目錄

      什么是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;

      • 常量池(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];

        }

      以上具體內(nèi)容可以參考

      1. JVM文檔

      2. 周志明的《深入理解Java虛擬機》,少見的國內(nèi)精品書籍

      3. 一些國外教程的解析

      3. Java Class加載的過程

      Class的加載主要分為兩步

      • 第一步通過ClassLoader進行讀取、連結操作

      • 第二步進行Class的<clinit>()初始化。

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

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

      Java反射在JVM的實現(xiàn)

      Class反序列化的流程

      • link: 根據(jù)Class結構體常量池進行符號的解引。比如對象計算內(nèi)存空間,創(chuàng)建方法表,native invoker,接口方法表,finalizer函數(shù)等工作。

      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

      }

      }

      具體參考如下:

      • When and how a Java class is loaded and initialized?

      • The Lifetime of a Type

      在完成初始化后,就是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)步驟如下

      1. 根據(jù)Class結構體信息,獲取field_count與fields[]字段,這個字段早已在load過程中被放入了

      2. 根據(jù)field_count的大小分配內(nèi)存、創(chuàng)建數(shù)組

      3. 將數(shù)組進行forEach循環(huán),通過fields[]中的信息依次創(chuàng)建Object對象

      4. 返回數(shù)組指針

      主要慢在如下方面

      1. 創(chuàng)建、計算、分配數(shù)組對象

      2. 對字段進行循環(huán)賦值

      4.3. Method.invoke的實現(xiàn)

      以下為無同步、無異常的情況下調(diào)用的步驟

      1. 創(chuàng)建Frame

      2. 如果對象flag為native,交給native_handler進行處理

      3. 在frame中執(zhí)行java代碼

      4. 彈出Frame

      5. 返回執(zhí)行結果的指針

      主要慢在如下方面

      1. 需要完全執(zhí)行ByteCode而缺少JIT等優(yōu)化

      2. 檢查參數(shù)非常多,這些本來可以在編譯器或者加載時完成

      4.4. class.newInstance的實現(xiàn)

      1. 檢測權限、預分配空間大小等參數(shù)

      2. 創(chuàng)建Object對象,并分配空間

      3. 通過Method.invoke調(diào)用構造函數(shù)(<init>())

      4. 返回Object指針

      主要慢在如下方面

      1. 參數(shù)檢查不能優(yōu)化或者遺漏

      2. <init>()的查表

      3. Method.invoke本身耗時

      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上認為反射比較慢的程序員主要有如下看法

      1. 驗證等防御代碼過于繁瑣,這一步本來在link階段,現(xiàn)在卻在計算時進行驗證

      2. 產(chǎn)生很多臨時對象,造成GC與計算時間消耗

      3. 由于缺少上下文,丟失了很多運行時的優(yōu)化,比如JIT(它可以看作JVM的重要評測標準之一)

      當然,現(xiàn)代JVM也不是非常慢了,它能夠?qū)Ψ瓷浯a進行緩存以及通過方法計數(shù)器同樣實現(xiàn)JIT優(yōu)化,所以反射不一定慢。

      更重要的是,很多情況下,你自己的代碼才是限制程序的瓶頸。因此,在開發(fā)效率遠大于運行效率的的基礎上,大膽使用反射,放心開發(fā)吧。

      學習Java的同學注意了?。?!

      學習過程中遇到什么問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:559743457【長按復制】 我們一起學Java!

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多