面試再問(wèn)你類(lèi)加載與雙親委派機(jī)制看這篇文章就夠了

類(lèi)加載機(jī)制

虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類(lèi)型,這就是虛擬機(jī)的類(lèi)加載機(jī)制。

類(lèi)加載的生命周期

類(lèi)從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個(gè)階段。其中驗(yàn)證、準(zhǔn)備、解析3個(gè)部分統(tǒng)稱(chēng)為連接(Linking)。

加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這5個(gè)階段的順序是確定的,而解析和使用是不一定保證順序的。

第一階段--加載

虛擬機(jī)規(guī)范中沒(méi)有強(qiáng)制什么時(shí)候加載,但是有初始化階段,這個(gè)階段是在加載-驗(yàn)證-準(zhǔn)備之后進(jìn)行的,只有以下五種情況必須對(duì)類(lèi)進(jìn)行初始化:

  1. 遇到new、getstatic、putstatic(讀取或設(shè)置一個(gè)靜態(tài)字段)或invokestatic(調(diào)用類(lèi)的靜態(tài)方法)這4條字節(jié)碼指令時(shí),如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化

2.反射方法調(diào)用

3.初始化時(shí),如果父類(lèi)還未初始化,先初始化父類(lèi)。

4.虛擬機(jī)啟動(dòng)時(shí)指定執(zhí)行一個(gè)main類(lèi)

5.當(dāng)使用JDK 1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。

過(guò)程:

虛擬機(jī)完成三件事:

  1. 通過(guò)一個(gè)類(lèi)全限定類(lèi)名獲取定義此類(lèi)的二進(jìn)制流
    2.將字節(jié)流中的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)
  2. 內(nèi)存中生成一個(gè)該類(lèi)Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)各種數(shù)據(jù)的訪問(wèn)入口

類(lèi)加載與接口加載區(qū)別:

接口與類(lèi)真正有所區(qū)別的是前面講述的5種“有且僅有”需要開(kāi)始初始化場(chǎng)景中的第3種:當(dāng)一個(gè)類(lèi)在初始化時(shí),要求其父類(lèi)全部都已經(jīng)初始化過(guò)了,但是一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候(如引用接口中定義的常量)才會(huì)初始化。

第二階段---驗(yàn)證

目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。

4個(gè)階段的檢驗(yàn)動(dòng)作:

  1. 文件格式驗(yàn)證
    驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理
    是否以魔數(shù)0xCAFEBABE開(kāi)頭。
    主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)。
    常量池的常量中是否有不被支持的常量類(lèi)型(檢查常量tag標(biāo)志)。
    指向常量的各種索引值中是否有指向不存在的常量或不符合類(lèi)型的常量。
    CONSTANT_Utf8_info型的常量中是否有不符合UTF8編碼的數(shù)據(jù)。

  2. 元數(shù)據(jù)驗(yàn)證
    對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析

  3. 字節(jié)碼驗(yàn)證
    確定程序語(yǔ)義是合法的、符合邏輯的。

  4. 符號(hào)引用驗(yàn)證
    最后一個(gè)階段的校驗(yàn)發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候,這個(gè)轉(zhuǎn)化動(dòng)作將在連接的第三階段——解析階段中發(fā)生。

第二階段是準(zhǔn)備階段

準(zhǔn)備階段是正式為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段。

第三階段是解析階段

解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,

初始化

? 類(lèi)初始化階段是類(lèi)加載過(guò)程的最后一步,前面的類(lèi)加載過(guò)程中,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類(lèi)加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開(kāi)始執(zhí)行類(lèi)中定義的Java程序代碼(或者說(shuō)是字節(jié)碼).

字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求

ClassLoader類(lèi)加載器定義

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類(lèi)加載階段中的“通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取描述此類(lèi)的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓?xiě)?yīng)用程序自己決定如何去獲取所需要的類(lèi)。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱(chēng)為“類(lèi)加載器”。

作用

負(fù)責(zé)加載class文件,class文件在文件開(kāi)頭有特定的文件標(biāo)示,并且ClassLoader只負(fù)責(zé)class文件的加載,至于它是否可以運(yùn)行,則由Execution Engine(執(zhí)行引擎:負(fù)責(zé)解釋命令,提交操作系
統(tǒng)執(zhí)行)決定


分類(lèi)

虛擬機(jī)自帶的加載器

? 啟動(dòng)類(lèi)加載器(Bootstrap)C++實(shí)現(xiàn)

啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):這個(gè)將負(fù)責(zé)將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是虛擬機(jī)識(shí)別的。僅按照文件名識(shí)別,如rt.jar,名字不符合的類(lèi)庫(kù)即使放在lib目錄中也不會(huì)被加載類(lèi)庫(kù)加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類(lèi)加載器無(wú)法被Java程序直接引用,用戶在編寫(xiě)自定義類(lèi)加載器時(shí),如果需要把加載請(qǐng)求委派給引導(dǎo)類(lèi)加載器,那直接使用null代替即可

其他類(lèi)加載器

? 擴(kuò)展類(lèi)加載器(Extension)Java

這個(gè)加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類(lèi)加載器。

? 應(yīng)用程序類(lèi)加載器(AppClassLoader) Java也叫系統(tǒng)類(lèi)加載器,加載當(dāng)前應(yīng)用的classpath的所有類(lèi)。

這個(gè)類(lèi)加載器由sun.misc.Launcher $App-ClassLoader實(shí)現(xiàn)。由于這個(gè)類(lèi)加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱(chēng)它為系統(tǒng)類(lèi)加載器。它負(fù)責(zé)加載用戶類(lèi)路徑(ClassPath)上所指定的類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用這個(gè)類(lèi)加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器,一般情況下這個(gè)就是程序中默認(rèn)的類(lèi)加載器。

? 用戶自定義加載器

Java.lang.ClassLoader的子類(lèi),用戶可以定制類(lèi)的加載方式。
具體實(shí)例如圖所示:



以上展示了類(lèi)加載器之間的層次關(guān)系,稱(chēng)為類(lèi)加載器的雙親委派模型。

雙親委派模型要求除了頂層的啟動(dòng)類(lèi)加載器外,其余的類(lèi)加載器都應(yīng)當(dāng)有自己的父類(lèi)加載器。這里類(lèi)加載器之間的父子關(guān)系一般不會(huì)以繼承(Inheritance)的關(guān)系來(lái)實(shí)現(xiàn),而是都使用組合(Composition)關(guān)系來(lái)復(fù)用父加載器的代碼。

那么我們?nèi)绾稳ヲ?yàn)證類(lèi)加載器的存在呢??
可以寫(xiě)一段測(cè)試代碼:

public class ClassLoadTest {
    public static void main(String[] args) {
        //輸出該類(lèi)的類(lèi)加載器的父類(lèi)的父類(lèi),應(yīng)該是bootstrap類(lèi)加載器
        System.out.println(new ClassLoadTest().getClass().getClassLoader().getParent().getParent());
        //輸出該類(lèi)的類(lèi)加載器的父類(lèi),ext 擴(kuò)展類(lèi)加載器
        System.out.println(new ClassLoadTest().getClass().getClassLoader().getParent());
        //輸出該類(lèi)的類(lèi)加載器
        System.out.println(new ClassLoadTest().getClass().getClassLoader());
        System.out.println("===================");
        System.out.println(new Object().getClass().getClassLoader());
        System.out.println("null 代表是bootstrap的classloader,會(huì)輸出為null");
    }
}

輸出:

看下這個(gè)例子:

public class String {
    public static void main(String[] args) {
        new String();
    }
}

運(yùn)行結(jié)果:

我們都知道,String屬于默認(rèn)加載類(lèi)的,在雙親委派機(jī)制中,上一級(jí)類(lèi)加載器先不加載,由最下面的開(kāi)始加載,如果下面的加載不到,再委派給上一級(jí)類(lèi)加載器去加載。

雙親委派機(jī)制是什么?

雙親委派模型的工作過(guò)程是:如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,每一個(gè)層次的類(lèi)加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類(lèi)加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類(lèi))時(shí),子加載器才會(huì)嘗試自己去加載。這個(gè)機(jī)制就叫雙親委派機(jī)制。

好處:
java類(lèi)隨著類(lèi)加載器一起具備了帶有優(yōu)先級(jí)的層次,比如Object類(lèi),它存放在rt.jar之中,無(wú)論哪一個(gè)類(lèi)加載器要加載這個(gè)類(lèi),最終都是委派給處于模型最頂端的啟動(dòng)類(lèi)加載器進(jìn)行加載,因此Object類(lèi)在程序的各種類(lèi)加載器環(huán)境中都是同一個(gè)類(lèi)。能夠保證加載的唯一性和是同一個(gè)類(lèi)。

相反,如果沒(méi)有使用雙親委派模型,由各個(gè)類(lèi)加載器自行去加載的話,如果用戶自己編寫(xiě)了一個(gè)稱(chēng)為java.lang.Object的類(lèi),并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類(lèi),Java類(lèi)型體系中最基礎(chǔ)的行為也就無(wú)法保證,應(yīng)用程序也將會(huì)變得一片混亂。

雙親委派機(jī)制的實(shí)現(xiàn)
1.首先,檢查請(qǐng)求的類(lèi)是否已經(jīng)被加載過(guò)了
2.未加載,則請(qǐng)求父類(lèi)加載器去加載對(duì)應(yīng)路徑下的類(lèi),
3.如果加載不到,才由下面的子類(lèi)依次去加載。

待續(xù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容