java 類加載機(jī)制和反射詳解及實(shí)例代碼
一、Java類加載機(jī)制
1.概述
Class文件由類裝載器裝載后,在JVM中將形成一份描述Class結(jié)構(gòu)的元信息對(duì)象,通過(guò)該元信息對(duì)象可以獲知Class的結(jié)構(gòu)信息:如構(gòu)造函數(shù),屬性和方法等,Java允許用戶借由這個(gè)Class相關(guān)的元信息對(duì)象間接調(diào)用Class對(duì)象的功能。
虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制。
2.工作機(jī)制
類裝載器就是尋找類的字節(jié)碼文件,并構(gòu)造出類在JVM內(nèi)部表示的對(duì)象組件。在Java中,類裝載器把一個(gè)類裝入JVM中,要經(jīng)過(guò)以下步驟:
(1) 裝載:查找和導(dǎo)入Class文件;
(2) 鏈接:把類的二進(jìn)制數(shù)據(jù)合并到JRE中;
(a)校驗(yàn):檢查載入Class文件數(shù)據(jù)的正確性;
(b)準(zhǔn)備:給類的靜態(tài)變量分配存儲(chǔ)空間;
(c)解析:將符號(hào)引用轉(zhuǎn)成直接引用;
(3) 初始化:對(duì)類的靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化操作
Java程序可以動(dòng)態(tài)擴(kuò)展是由運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)鏈接實(shí)現(xiàn)的;比如:如果編寫一個(gè)使用接口的應(yīng)用程序,可以等到運(yùn)行時(shí)再指定其實(shí)際的實(shí)現(xiàn)(多態(tài)),解析過(guò)程有時(shí)候還可以在初始化之后執(zhí)行;比如:動(dòng)態(tài)綁定(多態(tài));
【類初始化】
(1) 遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見(jiàn)的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。
(2) 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
(3) 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類的初始化。
(4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
只有上述四種情況會(huì)觸發(fā)初始化,也稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用,除此以外,所有其他方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用
代碼清單1
上述代碼運(yùn)行后,只會(huì)輸出【---SuperClass init】, 而不會(huì)輸出【SubClass init】,對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此,通過(guò)子類來(lái)調(diào)用父類的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化,但是這是要看不同的虛擬機(jī)的不同實(shí)現(xiàn)。
代碼清單2
此處不會(huì)引起SuperClass的初始化,但是卻觸發(fā)了【[Ltest.SuperClass】的初始化,通過(guò)arr.toString()可以看出,對(duì)于用戶代碼來(lái)說(shuō),這不是一個(gè)合法的類名稱,它是由虛擬機(jī)自動(dòng)生成的,直接繼承于Object的子類,創(chuàng)建動(dòng)作由字節(jié)碼指令newarray觸發(fā),此時(shí)數(shù)組越界檢查也會(huì)伴隨數(shù)組對(duì)象的所有調(diào)用過(guò)程,越界檢查并不是封裝在數(shù)組元素訪問(wèn)的類中,而是封裝在數(shù)組訪問(wèn)的xaload,xastore字節(jié)碼指令中.
代碼清單3
對(duì)常量ConstClass.value 的引用實(shí)際都被轉(zhuǎn)化為NotInitialization類對(duì)自身常量池的引用,這兩個(gè)類被編譯成class后不存在任何聯(lián)系。
【裝載】
在裝載階段,虛擬機(jī)需要完成以下3件事情
(1) 通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流
(2) 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
(3) 在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口。
虛擬機(jī)規(guī)范中并沒(méi)有準(zhǔn)確說(shuō)明二進(jìn)制字節(jié)流應(yīng)該從哪里獲取以及怎樣獲取,這里可以通過(guò)定義自己的類加載器去控制字節(jié)流的獲取方式。
【驗(yàn)證】
虛擬機(jī)如果不檢查輸入的字節(jié)流,對(duì)其完全信任的話,很可能會(huì)因?yàn)檩d入了有害的字節(jié)流而導(dǎo)致系統(tǒng)奔潰。
【準(zhǔn)備】
準(zhǔn)備階段是正式為類變量分配并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配,需要說(shuō)明的是:
這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中;這里所說(shuō)的初始值“通常情況”是數(shù)據(jù)類型的零值,假如:
public static int value = 123;
value在準(zhǔn)備階段過(guò)后的初始值為0而不是123,而把value賦值的putstatic指令將在初始化階段才會(huì)被執(zhí)行
二、類加載器與雙親委派模型
類加載器
(1) Bootstrap ClassLoader : 將存放于<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如 rt.jar 名字不符合的類庫(kù)即使放在lib目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類加載器無(wú)法被Java程序直接引用
(2) Extension ClassLoader : 將<JAVA_HOME>\lib\ext目錄下的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù)加載。開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。
(3) Application ClassLoader : 負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫(kù),開(kāi)發(fā)者可直接使用。
雙親委派模型
工作過(guò)程:如果一個(gè)類加載器接收到了類加載的請(qǐng)求,它首先把這個(gè)請(qǐng)求委托給他的父類加載器去完成,每個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它在搜索范圍中沒(méi)有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。
好處:java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放在rt.jar中,無(wú)論哪個(gè)類加載器要加載這個(gè)類,最終都會(huì)委派給啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果用戶自己寫了一個(gè)名為java.lang.Object的類,并放在程序的Classpath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,java類型體系中最基礎(chǔ)的行為也無(wú)法保證,應(yīng)用程序也會(huì)變得一片混亂。
java.lang.ClassLoader中幾個(gè)最重要的方法:
//加載指定名稱(包括包名)的二進(jìn)制類型,供用戶調(diào)用的接口 public Class<?> loadClass(String name); //加載指定名稱(包括包名)的二進(jìn)制類型,同時(shí)指定是否解析(但是,這里的resolve參數(shù)不一定真正能達(dá)到解析的效果),供繼承用 protected synchronized Class<?> loadClass(String name, boolean resolve); protected Class<?> findClass(String name) //定義類型,一般在findClass方法中讀取到對(duì)應(yīng)字節(jié)碼后調(diào)用,可以看出不可繼承(說(shuō)明:JVM已經(jīng)實(shí)現(xiàn)了對(duì)應(yīng)的具體功能,解析對(duì)應(yīng)的字節(jié)碼,產(chǎn)生對(duì)應(yīng)的內(nèi)部數(shù)據(jù)結(jié)構(gòu)放置到方法區(qū),所以無(wú)需覆寫,直接調(diào)用就可以了) protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{}
如下是實(shí)現(xiàn)雙親委派模型的主要代碼:
三、反射
Reflection機(jī)制允許程序在正在執(zhí)行的過(guò)程中,利用Reflection APIs取得任何已知名稱的類的內(nèi)部信息,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,并可以在執(zhí)行的過(guò)程中,動(dòng)態(tài)生成instances、變更fields內(nèi)容或喚起methods。
1、獲取構(gòu)造方法
Class類提供了四個(gè)public方法,用于獲取某個(gè)類的構(gòu)造方法。
Constructor getConstructor(Class[] params)
根據(jù)構(gòu)造函數(shù)的參數(shù),返回一個(gè)具體的具有public屬性的構(gòu)造函數(shù)
Constructor getConstructors()
返回所有具有public屬性的構(gòu)造函數(shù)數(shù)組
Constructor getDeclaredConstructor(Class[] params)
根據(jù)構(gòu)造函數(shù)的參數(shù),返回一個(gè)具體的構(gòu)造函數(shù)(不分public和非public屬性)
Constructor getDeclaredConstructors()
返回該類中所有的構(gòu)造函數(shù)數(shù)組(不分public和非public屬性)
2、獲取類的成員方法
與獲取構(gòu)造方法的方式相同,存在四種獲取成員方法的方式?!?/p>
Method getMethod(String name, Class[] params)
根據(jù)方法名和參數(shù),返回一個(gè)具體的具有public屬性的方法
Method[] getMethods()
返回所有具有public屬性的方法數(shù)組
Method getDeclaredMethod(String name, Class[] params)
根據(jù)方法名和參數(shù),返回一個(gè)具體的方法(不分public和非public屬性)
Method[] getDeclaredMethods()
返回該類中的所有的方法數(shù)組(不分public和非public屬性)
3、獲取類的成員變量(成員屬性)
存在四種獲取成員屬性的方法
Field getField(String name)
根據(jù)變量名,返回一個(gè)具體的具有public屬性的成員變量
Field[] getFields()
返回具有public屬性的成員變量的數(shù)組
Field getDeclaredField(String name)
根據(jù)變量名,返回一個(gè)成員變量(不分public和非public屬性)
Field[] getDelcaredFields()
返回所有成員變量組成的數(shù)組(不分public和非public屬性)
參考:
《深入理解JVM虛擬機(jī)》
感謝閱讀 ,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Flowable?設(shè)置任務(wù)處理人的四種方式詳解
這篇文章主要為大家介紹了Flowable?設(shè)置任務(wù)處理人的四種方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10SpringBoot返回前端Long類型字段丟失精度問(wèn)題及解決方案
Java服務(wù)端返回Long整型數(shù)據(jù)給前端,JS會(huì)自動(dòng)轉(zhuǎn)換為Number類型,本文主要介紹了SpringBoot返回前端Long類型字段丟失精度問(wèn)題及解決方案,感興趣的可以了解一下2024-03-03Caused by: java.io.IOException: DerInputStrea
這篇文章主要介紹了Caused by: java.io.IOException: DerInputStream.getLength(): lengthTag=111, too big,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-10-10SpringBoot簡(jiǎn)單實(shí)現(xiàn)定時(shí)器過(guò)程
這篇文章主要介紹了SpringBoot簡(jiǎn)單實(shí)現(xiàn)定時(shí)器過(guò)程,對(duì)于Java后端來(lái)說(shuō)肯定實(shí)現(xiàn)定時(shí)功能肯定是使用到Spring封裝好的定時(shí)調(diào)度Scheduled2023-04-04解決遇到Cannot resolve ch.qos.logback:logback-classic:
當(dāng)使用Maven配置項(xiàng)目依賴時(shí),可能會(huì)遇到無(wú)法解析特定版本的錯(cuò)誤,例如,logback-classic版本1.2.3可能無(wú)法在配置的倉(cāng)庫(kù)中找到,解決方法包括檢查倉(cāng)庫(kù)是否包含所需版本,或更新到其他可用版本,可通過(guò)Maven官網(wǎng)搜索并找到適用的版本,替換依賴配置中的版本信息2024-09-09ConcurrentHashMap是如何實(shí)現(xiàn)線程安全的你知道嗎
這篇文章主要介紹了ConcurrentHashMap是如何實(shí)現(xiàn)線程安全的你知道嗎,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10