Java類加載機(jī)制

1.虛擬機(jī)如何加載這些Class文件?(類加載的過程)
2.Class文件中的信息進(jìn)入到虛擬機(jī)后會(huì)發(fā)生什么變化?

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

類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括了:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸載(Unloading)七個(gè)階段。其中驗(yàn)證、準(zhǔn)備和解析三個(gè)部分統(tǒng)稱為連接(Linking),這七個(gè)階段的發(fā)生順序如下圖所示:

如上圖所示,加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這五個(gè)階段的順序是確定的,類的加載過程必須按照這個(gè)順序來按部就班地開始,而解析階段則不一定,它在某些情況下可以在初始化階段后再開始。

類的生命周期的每一個(gè)階段通常都是互相交叉混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過程中調(diào)用或激活另外一個(gè)階段。

一、類加載的時(shí)機(jī)

主動(dòng)引用:一個(gè)類被主動(dòng)引用之后會(huì)觸發(fā)初始化過程(加載,驗(yàn)證,準(zhǔn)備需再此之前開始)

必須馬上對(duì)類進(jìn)行初始化

1)遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這4條指令最常見的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í)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

3)當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要觸發(fā)父類的初始化。

4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會(huì)先初始化這個(gè)類。

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

被動(dòng)引用:一個(gè)類如果是被動(dòng)引用的話,該類不會(huì)觸發(fā)初始化過程

1)通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化。對(duì)于靜態(tài)字段,只有直接定義該字段的類才會(huì)被初始化,因此當(dāng)我們通過子類來引用父類中定義的靜態(tài)字段時(shí),只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化。 只有定義了這個(gè)字段的類才會(huì)被初始化

2)通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化。

3)常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。

二、類加載過程

1、加載

在加載階段,虛擬機(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.在java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。

相對(duì)于類加載過程的其他階段,加載階段是開發(fā)期相對(duì)來說可控性比較強(qiáng),該階段既可以使用系統(tǒng)提供的類加載器完成,也可以由用戶自定義的類加載器來完成,開發(fā)人員可以通過定義自己的類加載器去控制字節(jié)流的獲取方式。

2、驗(yàn)證

驗(yàn)證的目的是為了確保Class文件中的字節(jié)流包含的信息符合當(dāng)前虛擬機(jī)的要求,而且不會(huì)危害虛擬機(jī)自身的安全。不同的虛擬機(jī)對(duì)類驗(yàn)證的實(shí)現(xiàn)可能會(huì)有所不同,但大致都會(huì)完成以下四個(gè)階段的驗(yàn)證:文件格式的驗(yàn)證、元數(shù)據(jù)的驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。

1)文件格式的驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理,該驗(yàn)證的主要目的是保證輸入的字節(jié)流能正確地解析并存儲(chǔ) 于方法區(qū)之內(nèi)。經(jīng)過該階段的驗(yàn)證后,字節(jié)流才會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ),后面的三個(gè)驗(yàn)證都是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的。

2)元數(shù)據(jù)驗(yàn)證:對(duì)類的元數(shù)據(jù)信息進(jìn)行語(yǔ)義校驗(yàn)(其實(shí)就是對(duì)類中的各數(shù)據(jù)類型進(jìn)行語(yǔ)法校驗(yàn)),保證不存在不符合Java語(yǔ)法規(guī)范的元數(shù)據(jù)信息。

3)字節(jié)碼驗(yàn)證:該階段驗(yàn)證的主要工作是進(jìn)行數(shù)據(jù)流和控制流分析,對(duì)類的方法體進(jìn)行校驗(yàn)分析,以保證被校驗(yàn)的類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為。

4)符號(hào)引用驗(yàn)證:這是最后一個(gè)階段的驗(yàn)證,它發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候(解析階段中發(fā)生該轉(zhuǎn)化,后面會(huì)有講解),主要是對(duì)類自身以外的信息(常量池中的各種符號(hào)引用)進(jìn)行匹配性的校驗(yàn)。

3、準(zhǔn)備

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配。

注:

1)這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(static),而不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在Java堆中。

2)這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類型默認(rèn)的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

  1. 如果類字段的字段屬性表中存在ConstantValue屬性,在準(zhǔn)備階段該字段就會(huì)被初始化為ConstantValue所指定的值. final

4、解析

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

符號(hào)引用(Symbolic Reference):符號(hào)引用以一組符號(hào)來描述所引用的目標(biāo),符號(hào)引用可以是任何形式的字面量,符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)在內(nèi)存中。

直接引用(Direct Reference):直接引用可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個(gè)符號(hào)引用在不同的虛擬機(jī)實(shí)例上翻譯出來的直接引用一般都不相同,如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

1、類或接口的解析:判斷所要轉(zhuǎn)化成的直接引用是對(duì)數(shù)組類型,還是普通的對(duì)象類型的引用,從而進(jìn)行不同的解析。 加載引用的類

2、字段解析:對(duì)字段進(jìn)行解析時(shí),會(huì)先在本類中查找是否包含有簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段,如果有,則查找結(jié)束;如果沒有,則會(huì)按照繼承關(guān)系從上往下遞歸搜索該類所實(shí)現(xiàn)的各個(gè)接口和它們的父接口,還沒有,則按照繼承關(guān)系從上往下遞歸搜索其父類,直至查找結(jié)束。 (注意查找順序)

3、類方法解析:對(duì)類方法的解析與對(duì)字段解析的搜索步驟差不多,只是多了判斷該方法所處的是類還是接口的步驟,而且對(duì)類方法的匹配搜索,是先搜索父類,再搜索接口。

4、接口方法解析:與類方法解析步驟類似,只是接口不會(huì)有父類,因此,只遞歸向上搜索父接口就行了。

5、初始化

類初始化階段是類加載過程的最后一步,前面的類加載過程中,除了加載(Loading)階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼。

初始化階段是執(zhí)行類構(gòu)造器()方法的過程。

1)<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{}塊)中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序由語(yǔ)句在源文件中出現(xiàn)的順序所決定。(靜態(tài)語(yǔ)句塊中只能賦值,不能訪問,靜態(tài)語(yǔ)句塊只能訪問到定義在靜態(tài)語(yǔ)句塊之前的變量)

2)<clinit>()方法與類的構(gòu)造函數(shù)不同,它不需要顯式地調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢,因此在虛擬機(jī)中第一個(gè)執(zhí)行的<clinit>()方法的類一定是java.lang.Object。

3)由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作。

4)<clinit>()方法對(duì)于類或者接口來說并不是必需的,如果一個(gè)類中沒有靜態(tài)語(yǔ)句塊也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成<clinit>()方法。

5)接口中可能會(huì)有變量賦值操作,因此接口也會(huì)生成<clinit>()方法。但是接口與類不同,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法。只有當(dāng)父接口中定義的變量被使用時(shí),父接口才會(huì)被初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的<clinit>()方法。

6)虛擬機(jī)會(huì)保證一個(gè)類的()方法在多線程環(huán)境中被正確地加鎖和同步。如果有多個(gè)線程去同時(shí)初始化一個(gè)類,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法,其它線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢。如果在一個(gè)類的<clinit>()方法中有耗時(shí)很長(zhǎng)的操作,那么就可能造成多個(gè)進(jìn)程阻塞。

類加載器

通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流

類與類加載器

類名稱空間,比較兩個(gè)類是否相等,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義.

三、雙親委派模型

JVM預(yù)定義的三種類型類加載器:

1)啟動(dòng)(Bootstrap)類加載器:是用本地代碼實(shí)現(xiàn)的類裝入器,它負(fù)責(zé)將 /lib下面的類庫(kù)加載到內(nèi)存中(比如rt.jar)。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開發(fā)者無(wú)法直接獲取到啟動(dòng)類加載器的引用,所以不允許直接通過引用進(jìn)行操作。

2)標(biāo)準(zhǔn)擴(kuò)展(Extension)類加載器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將< Java_Runtime_Home >/lib/ext或者由系統(tǒng)變量 java.ext.dir指定位置中的類庫(kù)加載到內(nèi)存中。開發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。

3)系統(tǒng)(System)類加載器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫(kù)加載到內(nèi)存中。開發(fā)者可以直接使用系統(tǒng)類加載器。

雙親委派機(jī)制描述:

某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。

    /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     *
     * <p><ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     *
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     *
     *   <li><p> Invoke the {@link #findClass(String)} method to find the
     *   class.  </p></li>
     *
     * </ol>
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     *
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     *
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     *
     * @param  name
     *         The <a href="#name">binary name</a> of the class
     *
     * @param  resolve
     *         If <tt>true</tt> then resolve the class
     *
     * @return  The resulting <tt>Class</tt> object
     *
     * @throws  ClassNotFoundException
     *          If the class could not be found
     */
    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;
        }
    }

1.檢查類是否已經(jīng)被加載
2.如果沒有,就用父類的加載器
3.如果父類無(wú)法加載,然后調(diào)用findClass方法自行加載

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 概述 虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存中,并對(duì)數(shù)據(jù)進(jìn)行驗(yàn)證,準(zhǔn)備,解析,初始化的一個(gè)過程,最終是可以...
    Wen_Q_M閱讀 310評(píng)論 0 1
  • 一、類加載機(jī)制 1.定義: 把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成...
    Ruheng閱讀 2,713評(píng)論 6 36
  • 基于JVM的語(yǔ)言,如java,kotlin,groovy等語(yǔ)言,在各自編譯器編譯完成之后,都會(huì)編譯為.class文...
    Vinctor閱讀 1,185評(píng)論 2 5
  • Java的核心是 JVM ,了解并熟悉JVM對(duì)于我們理解Java語(yǔ)言非常重要。 一、類加載機(jī)制 當(dāng)程序主動(dòng)使用某個(gè)...
    年少懵懂丶流年夢(mèng)閱讀 1,168評(píng)論 2 15
  • 什么是虛擬機(jī)的類加載機(jī)制?虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析以及初始化,最...
    EakonZhao閱讀 2,470評(píng)論 6 22

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