日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

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

 小小郡 2016-09-19

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

本文目錄

什么是Java反射,有什么用?

Java Class文件的結(jié)構(gòu)

Java Class加載的過程

反射在native的實(shí)現(xiàn)

附錄

1、什么是Java反射,有什么用?

反射使程序代碼能夠接入裝載到JVM中的類的內(nèi)部信息,允許在編寫與執(zhí)行時(shí),而不是源代碼中選定的類協(xié)作的代碼,是以開發(fā)效率換運(yùn)行效率的一種手段。這使反射成為構(gòu)建靈活應(yīng)用的主要工具。

反射可以:

1、調(diào)用一些私有方法,實(shí)現(xiàn)黑科技。比如雙卡短信發(fā)送、設(shè)置狀態(tài)欄顏色、自動(dòng)掛電話等。

2、實(shí)現(xiàn)序列化與反序列化,比如PO的ORM,Json解析等。

3、實(shí)現(xiàn)跨平臺(tái)兼容,比如JDK中的SocketImpl的實(shí)現(xiàn)

4、通過xml或注解,實(shí)現(xiàn)依賴注入(DI),注解處理,動(dòng)態(tài)代理,單元測試等功能。比如Retrofit、Spring或者Dagger

2、Java Class文件的結(jié)構(gòu)

在*.class文件中,以Byte流的形式進(jìn)行Class的存儲(chǔ),通過一系列Load,Parse后,Java代碼實(shí)際上可以映射為下圖的結(jié)構(gòu)體,這里可以用javap命令或者IDE插件進(jìn)行查看。

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段,提供常量、字符串、方法名等值或者符號(hào)(可以看作偏移定值的指針)的存放

  • access_flags: 對(duì)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: 一個(gè)長度為u2的指針,指向常量池中真正的地址,將在Link階段進(jìn)行符號(hào)解引。

  • filed: 字段信息,結(jié)構(gòu)體如下

    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等索引,并指向常量池:

    它的結(jié)構(gòu)體如下,詳細(xì)在這里

    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虛擬機(jī)》,少見的國內(nèi)精品書籍

  3. 一些國外教程的解析

3. Java Class加載的過程

Class的加載主要分為兩步

  • 第一步通過ClassLoader進(jìn)行讀取、連結(jié)操作

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

3.1. Classloader加載過程

ClassLoader用于加載、連接、緩存Class,可以通過純Java或者native進(jìn)行實(shí)現(xiàn)。在JVM的native代碼中,ClassLoader內(nèi)部維護(hù)著一個(gè)線程安全的HashTable<String,Class>,用于實(shí)現(xiàn)對(duì)Class字節(jié)流解碼后的緩存,如果HashTable中已經(jīng)有了緩存,則直接返回緩存;反之,在獲得類名后,通過讀取文件、網(wǎng)絡(luò)上的class字節(jié)流反序列化為JVM中native的C結(jié)構(gòu)體,接著malloc內(nèi)存,并將指針緩存在HashTable中。

下面是非數(shù)組情況下ClassLoader的流程

  • find/load: 將文件反序列化為C結(jié)構(gòu)體。

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

Class反序列化的流程

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

3.2. 初始化過程

當(dāng)ClassLoader加載Class結(jié)束后,將進(jìn)行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的構(gòu)造<init>了,本文暫不討論。

4. 反射在native的實(shí)現(xiàn)

反射在Java中可以直接調(diào)用,不過最終調(diào)用的仍是native方法,以下為主流反射操作的實(shí)現(xiàn)。

4.1. Class.forName的實(shí)現(xiàn)

Class.forName可以通過包名尋找Class對(duì)象,比如Class.forName("java.lang.String")。

在JDK的源碼實(shí)現(xiàn)中,可以發(fā)現(xiàn)最終調(diào)用的是native方法forName0(),它在JVM中調(diào)用的實(shí)際是findClassFromClassLoader(),原理與ClassLoader的流程一樣,具體實(shí)現(xiàn)已經(jīng)在上面介紹過了。

4.2. getDeclaredFields的實(shí)現(xiàn)

在JDK源碼中,可以知道class.getDeclaredFields()方法實(shí)際調(diào)用的是native方法getDeclaredFields0(),它在JVM主要實(shí)現(xiàn)步驟如下

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

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

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

  4. 返回?cái)?shù)組指針

主要慢在如下方面

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

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

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

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

  1. 創(chuàng)建Frame

  2. 如果對(duì)象flag為native,交給native_handler進(jìn)行處理

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

  4. 彈出Frame

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

主要慢在如下方面

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

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

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

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

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

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

  4. 返回Object指針

主要慢在如下方面

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

  2. <init>()的查表

  3. Method.invoke本身耗時(shí)

5. 附錄

5.1. JVM與源碼閱讀工具的選擇

初次學(xué)習(xí)JVM時(shí),不建議去看Android Art、Hotspot等重量級(jí)JVM的實(shí)現(xiàn),它內(nèi)部的防御代碼很多,還有android與libcore、bionic庫緊密耦合,以及分層、內(nèi)聯(lián)甚至能把編譯器的語義分析繞進(jìn)去,因此找一個(gè)教學(xué)用的、嵌入式小型的JVM有利于節(jié)約自己的時(shí)間。因?yàn)橐郧罢垓v過OpenWrt,聽過有大神推薦過jamvm,只有不到200個(gè)源文件,非常適合學(xué)習(xí)。

在工具的選擇上,個(gè)人推薦SourceInsight。對(duì)比了好幾個(gè)工具clion,vscode,sublime,sourceinsight,只有sourceinsight對(duì)索引、符號(hào)表的解析最準(zhǔn)確。

5.2. 關(guān)于幾個(gè)ClassLoader

參考這里

ClassLoader0:native的classloader,在JVM中用C寫的,用于加載rt.jar的包,在Java中為空引用。

ExtClassLoader: 用于加載JDK中額外的包,一般不怎么用

AppClassLoader: 加載自己寫的或者引用的第三方包,這個(gè)最常見

例子如下

//sun.misc.Launcher$AppClassLoader@4b67cf4d

//which class you create or jars from thirdParty

//第一個(gè)非常有歧義,但是它的確是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中使用,通過設(shè)置一個(gè)臨時(shí)變量,可以向子類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,實(shí)現(xiàn)加密、壓縮、熱部署等功能,這個(gè)是大坑,晚點(diǎn)再開。

5.3. 反射是否慢?

在Stackoverflow上認(rèn)為反射比較慢的程序員主要有如下看法

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

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

  3. 由于缺少上下文,丟失了很多運(yùn)行時(shí)的優(yōu)化,比如JIT(它可以看作JVM的重要評(píng)測標(biāo)準(zhǔn)之一)

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

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

學(xué)習(xí)Java的同學(xué)注意了!?。?/strong>

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

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多