類加載機(jī)制

1 JVM整體的運行原理

(1)首先將“.java”代碼文件,編譯成“.class”字節(jié)碼文件。
(2)類加載器把“.class”字節(jié)碼文件中的類加載到JVM中。
(3)JVM來執(zhí)行我們寫好的那些類中的代碼。

2 類加載的時機(jī)

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

類的生命周期

加載、驗證、準(zhǔn)備、初始化和卸載這5個階段的順序是確定的,類的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開始,這是為了支持Java語言的運行時綁定(也稱為動態(tài)綁定或晚期綁定)。注意,這里寫的是按部就班地“開始”,而不是按部就班地“進(jìn)行”或“完成”,強(qiáng)調(diào)這點是因為這些階段通常都是互相交叉地混合式進(jìn)行的,通常會在一個階段執(zhí)行的過程中調(diào)用、激活另外一個階段。
什么情況下需要開始類加載過程的第一個階段:加載?Java虛擬機(jī)規(guī)范中并沒有進(jìn)行強(qiáng)制約束,這點可以交給虛擬機(jī)的具體實現(xiàn)來自由把握。但是對于初始化階段,虛擬機(jī)規(guī)范則是嚴(yán)格規(guī)定了有且只有5種情況必須立即對類進(jìn)行“初始化”(而加載、驗證、準(zhǔn)備自然需要在此之前開始):
(1)使用new關(guān)鍵字實例化對象的時候、讀取或設(shè)置一個類的靜態(tài)字段(被final修飾、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時候、以及調(diào)用一個類的靜態(tài)方法的時候。
(2)使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
(3)當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
(4)當(dāng)虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類),虛擬機(jī)會先初始化這個主類。
(5)當(dāng)使用JDK1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
注意:
(1)對于靜態(tài)字段,只有直接定義這個字段的類才會被初始化,因此通過子類來引用父類中定義的靜態(tài)字段,不會導(dǎo)致子類的初始化。

public class SuperClass {

    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 123;

}
public class SubClass extends SuperClass {
    
    static {
        System.out.println("SubClass init!");
    }
    
}
public class NotInitialization {
    
    public static void main(String[] args) {
        // 輸出SuperClass init!
        // 輸出123
        System.out.println(SubClass.value);
    }

}

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

public class SuperClass {

    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 123;

}
public class NotInitialization {
        
    public static void main(String[] args) {
        // 沒有輸出
        SuperClass[] sca = new SuperClass[10];
    }

}

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

public class ConstClass {
    
    static {
        System.out.println("ConstClass init!");
    }
    
    public static final String HELLOWORLD = "hello world";

}
public class NotInitialization {
        
    public static void main(String[] args) {
        // 只輸出hello world
        System.out.println(ConstClass.HELLOWORLD);
    }

}

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

3 類加載的過程
3.1 加載

加載是類加載過程的一個階段。在加載階段,虛擬機(jī)需要完成一下3件事情:
(1)通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
(2)將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
(3)在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

3.2 驗證

驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。
驗證階段大致上會完成4個階段的檢驗動作:文件格式驗證、元數(shù)據(jù)驗證、字節(jié)碼驗證、符號引用驗證。

3.3 準(zhǔn)備

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。
這時候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在堆內(nèi)存中。
假設(shè)一個類變量的定義為“public static int value = 123;”,那變量value在準(zhǔn)備階段過后的初始值為0而不是123;假設(shè)上面類變量value的定義變?yōu)椤皃ublic static final int value = 123;”,在準(zhǔn)備階段虛擬機(jī)就會將value賦值為123。

3.4 解析

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

3.5 初始化

初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。
(1)<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的,編譯器收集的順序是由語句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語句塊可以賦值,但是不能訪問。
(2)虛擬機(jī)會保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。因此在虛擬機(jī)中第一個被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object。
(3)由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作。
(4)<clinit>()方法對于類或接口來說并不是必需的。
(5)接口中不能使用靜態(tài)語句塊,但仍然有變量初始化的賦值操作。接口與類不同的是,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法。只有當(dāng)父接口中定義的變量使用時,父接口才會初始化。另外,接口的實現(xiàn)類在初始化時也一樣不會執(zhí)行接口的<clinit>()方法。
(6)虛擬機(jī)會保證一個類的<clinit>()方法在多線程環(huán)境中被正確地加鎖、同步,如果多個線程同時去初始化一個類,那么只會有一個線程去執(zhí)行這個類地<clinit>()方法,其它線程都需要阻塞等待,直到活動現(xiàn)場執(zhí)行<clinit>()方法完畢。需要注意的是,其它線程雖然會被阻塞,但如果執(zhí)行<clinit>()方法的那條線程退出<clinit>()方法后,其它線程喚醒之后不會再次進(jìn)入<clinit>()方法。同一個類加載器下,一個類型只會初始化一次。

4 類加載器

類加載器雖然只用于實現(xiàn)類的加載動作,但它在Java程序中起到的作用卻遠(yuǎn)遠(yuǎn)不限于類的加載階段。對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。通俗地說,比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個class文件,被同一個虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個類必定不相等。

4.1 類加載器的種類

(1)啟動類加載器(Bootstrap ClassLoader):C++編寫。加載$JAVAHOME/jre/lib中的所有類庫。
(2)擴(kuò)展類加載器(Extension ClassLoader):Java編寫。加載$JAVAHOME/jre/lib/ext或者系統(tǒng)變量java.ext.dirs指定的目錄中的所有類庫。
(3)應(yīng)用程序類加載器(Application ClassLoader):Java編寫。加載用戶類路徑(ClassPath)上所指定的類庫,開發(fā)者可以直接使用這個類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認(rèn)的類加載器。
(4)自定義類加載器:Java編寫。

4.2 雙親委派模型

類加載器雙親委派模型

雙親委派模型的工作過程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。
使用雙親委派模型來組織類加載器之間的關(guān)系,有一個顯而易見的好處就是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系。如果沒有使用雙親委派模型,由各個類加載器自行去加載的話,如果用戶自己編寫了一個稱為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會出現(xiàn)多個不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無法保證,應(yīng)用程序也將會變得一片混亂。如果讀者有興趣的話,可以嘗試去編寫一個與rt.jar類庫中已有類重名的Java類,將會發(fā)現(xiàn)可以正常編譯,但永遠(yuǎn)無法被加載運行。

5 loadClass和forName的區(qū)別

Class.forName得到的class是已經(jīng)初始化完成的,ClassLoader.loadClass得到的class是還沒有連接的。

public class Robot {

    static {
        System.out.println("Hello Robot");
    }

}
public class LoadDifference {

    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader loader = Robot.class.getClassLoader();
        Class c1 = loader.loadClass("load.Robot");// 不打印
        Class c2 = Class.forName("load.Robot");// 打印
    }

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

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

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