深入理解Java:類加(jia)載機制及反(fan)射
說明(ming):本文乃學習整理(li)參考而來(lai).
一、Java類加載機制
1.概述
Class文件由(you)類裝(zhuang)(zhuang)載器裝(zhuang)(zhuang)載后,在JVM中將形成(cheng)一份描述Class結構(gou)的(de)元信息(xi)對象(xiang)(xiang),通過該元信息(xi)對象(xiang)(xiang)可以獲知Class的(de)結構(gou)信息(xi):如構(gou)造(zao)函數,屬性和方法等,Java允(yun)許用戶借由(you)這個(ge)Class相關的(de)元信息(xi)對象(xiang)(xiang)間接調用Class對象(xiang)(xiang)的(de)功(gong)能。
虛擬機把(ba)描述(shu)類的數據從class文件加(jia)載(zai)到內存,并對(dui)數據進(jin)行校驗,轉換解析和初始化,最終形成可(ke)以被(bei)虛擬機直接(jie)使用(yong)的Java類型(xing),這就(jiu)是虛擬機的類加(jia)載(zai)機制。
2.工作(zuo)機(ji)制
類(lei)裝載(zai)器就(jiu)是尋找類(lei)的(de)字(zi)節碼文件,并構造出(chu)類(lei)在(zai)JVM內(nei)部表示的(de)對象組(zu)件。在(zai)Java中,類(lei)裝載(zai)器把一個(ge)類(lei)裝入JVM中,要經(jing)過以下步(bu)驟(zou):
(1) 裝載:查找(zhao)和(he)導入Class文件;
(2) 鏈(lian)接(jie):把類的二進制數(shu)據合并到(dao)JRE中(zhong);
(a)校(xiao)驗:檢查載入Class文(wen)件數據的正確性;
(b)準備:給類的(de)靜(jing)態變量(liang)分(fen)配存儲空間(jian);
(c)解(jie)析(xi):將(jiang)符號引(yin)用(yong)轉成(cheng)直接引(yin)用(yong);
(3) 初始化(hua):對類(lei)的靜態(tai)變量,靜態(tai)代(dai)碼塊執(zhi)行(xing)初始化(hua)操作

Java程(cheng)序(xu)可以動(dong)(dong)態擴(kuo)展是(shi)由運(yun)行(xing)期(qi)動(dong)(dong)態加載和動(dong)(dong)態鏈接實(shi)(shi)現(xian)的;比(bi)如(ru):如(ru)果編(bian)寫(xie)一個使(shi)用接口的應用程(cheng)序(xu),可以等到運(yun)行(xing)時(shi)(shi)再指(zhi)定其實(shi)(shi)際的實(shi)(shi)現(xian)(多態),解析過程(cheng)有(you)時(shi)(shi)候還可以在初(chu)始化(hua)之后執行(xing);比(bi)如(ru):動(dong)(dong)態綁定(多態);
【類(lei)初始(shi)化】
(1) 遇到new、getstatic、putstatic或invokestatic這4條(tiao)字節(jie)碼(ma)指(zhi)令時(shi)(shi),如果類沒有進行過初(chu)始(shi)化(hua),則(ze)需要先觸發(fa)其初(chu)始(shi)化(hua)。生成這4條(tiao)指(zhi)令的(de)(de)(de)最常(chang)見的(de)(de)(de)Java代碼(ma)場(chang)景(jing)是:使用new關(guan)鍵(jian)字實例化(hua)對象的(de)(de)(de)時(shi)(shi)候(hou),讀取或設置一(yi)個(ge)類的(de)(de)(de)靜(jing)(jing)態字段(duan)(被(bei)final修(xiu)飾、已在編譯期把結果放入常(chang)量池的(de)(de)(de)靜(jing)(jing)態字段(duan)除外(wai))的(de)(de)(de)時(shi)(shi)候(hou),以及調用一(yi)個(ge)類的(de)(de)(de)靜(jing)(jing)態方法(fa)的(de)(de)(de)時(shi)(shi)候(hou)。
(2) 使用(yong)java.lang.reflect包的(de)方法對類進(jin)行反射調用(yong)的(de)時候,如果類沒有進(jin)行過初始(shi)化,則(ze)需要(yao)先觸發其初始(shi)化。
(3) 當(dang)初始化一個類的(de)時候(hou),如果發現其(qi)父(fu)類還沒有進行過初始化,則需要先觸發其(qi)父(fu)類的(de)初始化。
(4)當虛(xu)擬機(ji)啟動(dong)時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛(xu)擬機(ji)會先(xian)初(chu)始化這(zhe)個主類。
只有上述(shu)四(si)種情況會觸發初始化(hua),也稱為對一個類進行主動引用,除此以外,所有其(qi)他方式都不會觸發初始化(hua),稱為被動引用
代碼清(qing)單1

上述代碼運行后,只會輸出【---SuperClass init】, 而不會輸出【SubClass init】,對于靜態(tai)字段,只有直接定(ding)義這(zhe)個字段的類才會被(bei)初始化,因此,通過子類來調用父類的靜態字段,只會觸發父類的初始化,但是這是要看不同的虛擬機的不同實現。
代碼清單2

此處不會引(yin)起SuperClass的初(chu)(chu)始化(hua),但是(shi)卻(que)觸發(fa)了(le)【[Ltest.SuperClass】的初(chu)(chu)始化(hua),通過(guo)(guo)arr.toString()可(ke)以(yi)看出,對(dui)于用(yong)戶代碼來說,這不是(shi)一個合(he)法的類名(ming)稱,它是(shi)由(you)虛擬機自動生成的,直接繼承于Object的子類,創建動作由(you)字節(jie)(jie)碼指令newarray觸發(fa),此時數組(zu)越界(jie)檢查(cha)也(ye)會伴隨數組(zu)對(dui)象的所有調用(yong)過(guo)(guo)程,越界(jie)檢查(cha)并不是(shi)封裝(zhuang)在數組(zu)元素訪問(wen)(wen)的類中,而(er)是(shi)封裝(zhuang)在數組(zu)訪問(wen)(wen)的xaload,xastore字節(jie)(jie)碼指令中.
代碼清單3

對常(chang)量ConstClass.value 的(de)引(yin)用(yong)實際(ji)都被轉(zhuan)化為NotInitialization類對自身常(chang)量池的(de)引(yin)用(yong),這兩個(ge)類被編譯成class后不存在任何聯(lian)系。
【裝載】
在裝載階段,虛擬機需要(yao)完(wan)成(cheng)以下3件事情
(1) 通過一個類(lei)的全限定名來(lai)獲(huo)取定義(yi)此類(lei)的二進(jin)制字節流(liu)
(2) 將這(zhe)個字節流所代表的(de)靜態存儲(chu)結(jie)(jie)構轉化(hua)為方法(fa)區的(de)運行(xing)時(shi)數據結(jie)(jie)構
(3) 在Java堆中生成一個代表這(zhe)個類的java.lang.Class對象,作(zuo)為方法(fa)區這(zhe)些數據的訪(fang)問入口。
虛擬機規范中并沒有準確說明(ming)二進制(zhi)字(zi)節(jie)流應該從哪里獲取以及怎樣獲取,這里可以通過定義自己的類加(jia)載器(qi)去控制(zhi)字(zi)節(jie)流的獲取方式。
【驗證】
虛擬機如果不檢查輸入的字節(jie)流,對其完全信(xin)任(ren)的話,很可能(neng)會因為載入了有害的字節(jie)流而導(dao)致系統奔潰(kui)。
【準備】
準備(bei)階段(duan)是(shi)正式(shi)為類變量分配并設置類變量初始值的(de)階段(duan),這些內存都將在方法(fa)區中(zhong)進行分配,需要(yao)說明的(de)是(shi):
這(zhe)時(shi)候進(jin)行內存分(fen)配的僅包括(kuo)類變量(liang)(被(bei)static修(xiu)飾(shi)的變量(liang)),而不包括(kuo)實例(li)變量(liang),實例(li)變量(liang)將會在(zai)對(dui)象(xiang)(xiang)實例(li)化時(shi)隨著(zhu)對(dui)象(xiang)(xiang)一起分(fen)配在(zai)Java堆中;這(zhe)里所說的初始值“通常(chang)情況”是數據類型的零值,假如(ru):
public static int value = 123;
value在(zai)準(zhun)備階段(duan)過后的(de)(de)初始(shi)值(zhi)為(wei)0而不(bu)是123,而把(ba)value賦(fu)值(zhi)的(de)(de)putstatic指令將(jiang)在(zai)初始(shi)化階段(duan)才會被執行
二、類加載器與雙親委派模型
類(lei)加載器
(1) Bootstrap ClassLoader : 將(jiang)存放(fang)于(yu)<JAVA_HOME>\lib目錄(lu)中的(de)(de),或者(zhe)被-Xbootclasspath參(can)數(shu)所指定的(de)(de)路徑(jing)中的(de)(de),并且是(shi)虛擬機(ji)識別(bie)的(de)(de)(僅按照(zhao)文件名識別(bie),如(ru) rt.jar 名字不(bu)符合的(de)(de)類庫即使放(fang)在lib目錄(lu)中也(ye)不(bu)會被加(jia)(jia)載)類庫加(jia)(jia)載到虛擬機(ji)內存中。啟動類加(jia)(jia)載器無(wu)法被Java程序(xu)直接(jie)引用(yong)
(2) Extension ClassLoader : 將<JAVA_HOME>\lib\ext目錄下(xia)的(de)(de),或(huo)者(zhe)(zhe)被java.ext.dirs系統變量所指定(ding)的(de)(de)路徑(jing)中的(de)(de)所有類庫(ku)加(jia)載(zai)(zai)。開發者(zhe)(zhe)可以直接使(shi)用擴展類加(jia)載(zai)(zai)器。
(3) Application ClassLoader : 負責加載用戶類路徑(ClassPath)上所指定(ding)的類庫,開發者可直接使(shi)用。
雙親委派模型

工作過程:如果(guo)一個(ge)(ge)類(lei)加(jia)載(zai)(zai)(zai)器接收到了類(lei)加(jia)載(zai)(zai)(zai)的(de)請(qing)求(qiu),它首先把(ba)這個(ge)(ge)請(qing)求(qiu)委托給他的(de)父類(lei)加(jia)載(zai)(zai)(zai)器去完成,每個(ge)(ge)層次(ci)的(de)類(lei)加(jia)載(zai)(zai)(zai)器都(dou)是如此(ci),因此(ci)所(suo)有(you)(you)的(de)加(jia)載(zai)(zai)(zai)請(qing)求(qiu)都(dou)應該傳送到頂層的(de)啟(qi)動類(lei)加(jia)載(zai)(zai)(zai)器中(zhong),只有(you)(you)當(dang)父加(jia)載(zai)(zai)(zai)器反(fan)饋自己(ji)無法完成這個(ge)(ge)加(jia)載(zai)(zai)(zai)請(qing)求(qiu)(它在搜索范圍中(zhong)沒有(you)(you)找到所(suo)需的(de)類(lei))時,子加(jia)載(zai)(zai)(zai)器才會嘗試自己(ji)去加(jia)載(zai)(zai)(zai)。
好處:java類(lei)(lei)(lei)隨著它的類(lei)(lei)(lei)加(jia)載(zai)(zai)器(qi)一(yi)(yi)起具備了(le)一(yi)(yi)種(zhong)帶有優先級的層次關系(xi)。例如類(lei)(lei)(lei)java.lang.Object,它存(cun)放在(zai)rt.jar中(zhong)(zhong),無論哪個(ge)類(lei)(lei)(lei)加(jia)載(zai)(zai)器(qi)要加(jia)載(zai)(zai)這個(ge)類(lei)(lei)(lei),最終都(dou)會委派給啟動類(lei)(lei)(lei)加(jia)載(zai)(zai)器(qi)進行加(jia)載(zai)(zai),因此Object類(lei)(lei)(lei)在(zai)程序(xu)的各種(zhong)類(lei)(lei)(lei)加(jia)載(zai)(zai)器(qi)環境中(zhong)(zhong)都(dou)是同(tong)一(yi)(yi)個(ge)類(lei)(lei)(lei)。相反,如果用戶自己寫了(le)一(yi)(yi)個(ge)名為(wei)java.lang.Object的類(lei)(lei)(lei),并放在(zai)程序(xu)的Classpath中(zhong)(zhong),那系(xi)統(tong)中(zhong)(zhong)將會出現多個(ge)不同(tong)的Object類(lei)(lei)(lei),java類(lei)(lei)(lei)型體系(xi)中(zhong)(zhong)最基礎的行為(wei)也(ye)無法保證,應用程序(xu)也(ye)會變得一(yi)(yi)片混(hun)亂。
java.lang.ClassLoader中幾個最重要的方法:
//加載指定名稱(包括包名)的二(er)進制類型(xing),供用(yong)戶調用(yong)的接口
public Class<?> loadClass(String name);
//加載指定名稱(包括包名)的二進制類型(xing),同(tong)時指定是(shi)否解析(xi)(但是(shi),這里(li)的resolve參數不一定真正能(neng)達到解析(xi)的效果),供繼承用
protected synchronized Class<?> loadClass(String name, boolean resolve);
protected Class<?> findClass(String name)
//定義類型,一般在findClass方法中讀取到(dao)對應字(zi)節碼(ma)后調用,可以(yi)看出(chu)不(bu)可繼承(說(shuo)明:JVM已(yi)經實現了對應的具體功能,解析對應的字(zi)節碼(ma),產生(sheng)對應的內部數據結構放置到(dao)方法區(qu),所以(yi)無需覆寫,直(zhi)接調用就可以(yi)了)
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}
如下是(shi)實現雙親委派模型的主要(yao)代碼:

三、反射
Reflection機(ji)制(zhi)允(yun)許程(cheng)序在(zai)正在(zai)執行的(de)(de)過程(cheng)中(zhong),利用Reflection APIs取(qu)得任何(he)已知(zhi)名稱的(de)(de)類的(de)(de)內部(bu)信息,包(bao)括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可(ke)以在(zai)執行的(de)(de)過程(cheng)中(zhong),動態生(sheng)成instances、變更(geng)fields內容或(huo)喚起methods。
1、獲取(qu)構造方法
Class類提供了四個public方(fang)(fang)法(fa),用于獲取(qu)某個類的構(gou)造方(fang)(fang)法(fa)。
Constructor getConstructor(Class[] params)
根據構造函(han)數的參數,返回一個具體的具有(you)public屬性的構造函(han)數
Constructor getConstructors()
返回所有(you)具有(you)public屬性的構(gou)造函數數組
Constructor getDeclaredConstructor(Class[] params)
根(gen)據構(gou)造(zao)函(han)數的參數,返回一個具體的構(gou)造(zao)函(han)數(不分public和非public屬性)
Constructor getDeclaredConstructors()
返(fan)回(hui)該類中所有(you)的構造函數(shu)數(shu)組(不(bu)分public和非public屬性)

2、獲取類的成員方法
與獲取構造方(fang)(fang)法的方(fang)(fang)式(shi)相同(tong),存在四種獲取成員方(fang)(fang)法的方(fang)(fang)式(shi)。
Method getMethod(String name, Class[] params)
根(gen)據方法名和參數,返回一個具體的具有public屬(shu)性的方法
Method[] getMethods()
返回所有(you)具有(you)public屬性的方法數組
Method getDeclaredMethod(String name, Class[] params)
根據方法名和參數,返(fan)回一(yi)個具體的方法(不分public和非public屬性)
Method[] getDeclaredMethods()
返回該類(lei)中的所有的方法數組(不分public和非public屬性(xing))

3、獲取類(lei)的成(cheng)(cheng)員(yuan)變(bian)量(成(cheng)(cheng)員(yuan)屬性)
存在四種(zhong)獲取成員屬性的(de)方法
Field getField(String name)
根據變(bian)量名,返回一個(ge)具(ju)體的(de)具(ju)有public屬性的(de)成員(yuan)變(bian)量
Field[] getFields()
返回具有public屬性的(de)成員(yuan)變量的(de)數組
Field getDeclaredField(String name)
根據變量(liang)名,返(fan)回一個成員變量(liang)(不分public和非public屬(shu)性)
Field[] getDelcaredFields()
返回所有成員變量組成的數(shu)組(不分public和非public屬性)
參考:
《深入(ru)理解JVM虛擬機》
