Java的類加載機(jī)制

類加載過程

類從被加載到虛擬機(jī)內(nèi)存開始,直到被卸載出內(nèi)存為止,它的整個(gè)生命周期過程是:

加載 ---> 驗(yàn)證 ---> 準(zhǔn)備 ---> 解析 ---> 初始化 ---> 使用 ---> 卸載

加載

加載是類加載過程的第一個(gè)階段,在加載階段虛擬機(jī)需要完成三件事。

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

類加載器

類的加載的全都是交給類加載器去實(shí)現(xiàn)的。

在Java虛擬機(jī)規(guī)范中,加載器是分為兩大類:

  1. 引導(dǎo)類加載器

    引導(dǎo)類加載器是使用本地代碼實(shí)現(xiàn)的類加載器,負(fù)責(zé)將<JAVA_HOME>/lib下的核心類庫或-Xbootclasspath選項(xiàng)指定的jar包給加載到內(nèi)存中。

    這個(gè)加載器是屬于虛擬機(jī)部分的類,Java中是無法獲取到的

  2. 自定義加載器

    自定義加載器可以分為三類:

    • 擴(kuò)展類加載器

      擴(kuò)展類加載器是負(fù)責(zé)將<JAVA_HOME>/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中的類庫加載到內(nèi)存中

    • 應(yīng)用程序類加載器

      應(yīng)用程序類加載器是將classPath中所指定的類,一般來說Java類都是由這個(gè)類加載器來加載的

    • 自定義類加載器

      除了系統(tǒng)提供的類之外,也可以通過繼承java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器

類的加載順序 ----- 雙親委派模型

他們之間的關(guān)系如下圖所示:


image

JVM加載類的時(shí)候默認(rèn)采用的是雙親委派機(jī)制,
也是是說在某個(gè)類加載器在接收到加載類的請求的時(shí)候,
首先是將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器能夠完成加載就成功返回,
只有當(dāng)父類加載器無法完成加載任務(wù)時(shí)候,才會(huì)去調(diào)用自己去加載類。

在JDK中有默認(rèn)的擴(kuò)展類加載器和應(yīng)用程序類加載器

sun.misc.Launcher$ExtClassLoader 和 sun.misc.Launcher$AppClassLoader

可以用代碼看一下他們的加載關(guān)系:

public class Main {
    public static void main(String[] args) {
        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
    }
}

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

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@60e53b93
null

這個(gè)運(yùn)行結(jié)果能夠驗(yàn)證我們之前雙親委派模型的圖,應(yīng)用程序類加載器調(diào)用擴(kuò)展類加載器最后調(diào)用一個(gè)訪問不到的引導(dǎo)類加載器

他們的依賴關(guān)系:
<figure class="half"> <img src="http://ztianzeng.com/images/Java的類加載機(jī)制/yilaiguanxi.jpg" alt="圖片說明" > <img src="http://ztianzeng.com/images/Java的類加載機(jī)制/yilaiguanxi2.jpg" alt="圖片說明" > </figure>

他們最后都是調(diào)用ClassLoader這個(gè)里面的loadClass實(shí)現(xiàn)類加載

雙親委派模型的類加載邏輯:

  1. 首先檢查c有沒有被加載
  2. 如果沒有沒加載,查看父類加載器是否為空,為空調(diào)用父類加載器加載,否則調(diào)用引導(dǎo)類加載器加載
  3. 如果c在父類加載器加載下沒有加載成功,會(huì)調(diào)用當(dāng)前加載器的findClass進(jìn)行加載
  4. 若class最終被加載,且resolve為true,則對該class進(jìn)行鏈接操作
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

驗(yàn)證

驗(yàn)證階段用于確保類或接口的二進(jìn)制結(jié)構(gòu)上是正確的。需要滿足Java虛擬機(jī)限制中描述的靜態(tài)或者結(jié)構(gòu)上的約束

準(zhǔn)備

準(zhǔn)備階段的任務(wù)是為類或接口的靜態(tài)字段分配空間,并用初始值初始化這些字段,這個(gè)階段不會(huì)執(zhí)行任何的虛擬機(jī)字節(jié)碼指令。
也是就說不會(huì)初始化任何實(shí)例變量。

初始值是指Java虛擬機(jī)規(guī)范所支持的數(shù)據(jù)類型和對應(yīng)的初始值。
比如:

    public static int value = 2;

它最準(zhǔn)備階段被賦予的初始值是0而不是2。

在類的對象創(chuàng)建之后的任何時(shí)間,都可以進(jìn)行準(zhǔn)備階段,但它得確保一定要在初始化階段開始前完成。

解析

解析是根據(jù)運(yùn)行時(shí)常量池的符號(hào)引用來動(dòng)態(tài)決定具體的值的過程。
總共要解析六種類型:

  • 類與接口解析

  • 字段解析

  • 普通方法解析

  • 接口方法解析

  • 方法類型與方法句柄解析

  • 調(diào)用點(diǎn)限定符解析

初始化

初始化過程才是虛擬機(jī)真正執(zhí)行Java代碼的過程。

會(huì)觸發(fā)初始化過程的有這幾種行為:

  1. 在被選為Java啟動(dòng)的初始類
  2. 在對于某個(gè)類的子類初始化時(shí)
  3. 在調(diào)用JDK類庫的反射方法時(shí)
  4. 在調(diào)用java.lang.invoke.MethodHandle時(shí),如果檢測出來有static方法時(shí)
  5. 在執(zhí)行new方法、初始代碼塊或靜態(tài)代碼塊時(shí)

在多線程環(huán)境下,虛擬機(jī)會(huì)保證一個(gè)類的初始化方法會(huì)被正確的加鎖和同步。

使用

在初始化完成之后才能夠?qū)︻愡M(jìn)行調(diào)用

卸載

卸載時(shí)Java虛擬機(jī)將類移出內(nèi)存的過程。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容