欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入了解Java中的類加載機(jī)制

 更新時(shí)間:2022年11月15日 15:15:01   作者:夕陽(yáng)醉了  
通常,在關(guān)于Java的類加載部分會(huì)遇到以上疑問,本文將對(duì)類加載重要部分做詳細(xì)介紹,包括重要的基礎(chǔ)概念和應(yīng)用場(chǎng)景,在編寫過程中也幫助作者重新熟悉并加固了知識(shí)點(diǎn),希望在看完后對(duì)大家能有所幫助

一、類加載過程

程序員編寫的Java源程序(.java文件)在經(jīng)過編譯器編譯之后被轉(zhuǎn)換成字節(jié)代碼(.class 文件),類加載器將.class文件中的二進(jìn)制數(shù)據(jù)讀入到內(nèi)存中,將其放在方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對(duì)象,用來(lái)封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。

類加載的最終產(chǎn)品是位于堆區(qū)中的Class對(duì)象,Class對(duì)象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。

以下是舉例說明類加載過程:

二、類生命周期

類的生命周期包括:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載7個(gè)階段。其中加載、驗(yàn)證、準(zhǔn)備、初始化、卸載5個(gè)階段是按照這種順序按部就班的開始,而解析階段則不一定:某些情況下,可以在初始化之后再開始,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定或晚期綁定,其實(shí)就是多態(tài)),例如子類重寫父類方法。

注意:這里寫的是按部就班的開始,而不是按部就班地進(jìn)行或完成,因?yàn)檫@些階段通常都是互相交叉混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行過程中調(diào)用、激活另外一個(gè)階段。

1、加載

加載階段會(huì)做3件事情:

  • 通過一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流。
  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
  • 在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問入口。

此處第一點(diǎn)并沒指明要從哪里獲取、怎樣獲取,因此這里給開發(fā)人員預(yù)留了擴(kuò)展空間。許多Java技術(shù)就建立在此基礎(chǔ)上,例如:

  • 從ZIP包讀取,如JAR、WAR。
  • 從網(wǎng)絡(luò)中獲取,這種場(chǎng)景最典型應(yīng)用場(chǎng)景應(yīng)用就是Applet。
  • 運(yùn)行時(shí)計(jì)算生成,使用較多場(chǎng)景是動(dòng)態(tài)代理技術(shù),如spring AOP。

加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,而且在Java堆中也創(chuàng)建一個(gè)java.lang.Class類的對(duì)象,這樣便可以通過該對(duì)象訪問方法區(qū)中的這些數(shù)據(jù)。

2、驗(yàn)證

確保被加載的類的正確性,分為4個(gè)驗(yàn)證階段:

  • 文件格式驗(yàn)證
  • 元數(shù)據(jù)驗(yàn)證
  • 字節(jié)碼驗(yàn)證
  • 符號(hào)引用驗(yàn)證

驗(yàn)證階段非常重要的,但不是必須的,它對(duì)程序運(yùn)行期沒有影響,如果所引用的類經(jīng)過反復(fù)驗(yàn)證,那么可以考慮采用-Xverifynone參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載的時(shí)間。

3、準(zhǔn)備

為類的靜態(tài)變量分配內(nèi)存,并初始化默認(rèn)值,這些內(nèi)存是在方法區(qū)中分配,需要注意以下幾點(diǎn):

  • 此處內(nèi)存分配的變量?jī)H包含類變量(static),而不包括實(shí)例變量,實(shí)例變量會(huì)隨著對(duì)象實(shí)例化被分配在java堆中。
  • 這里默認(rèn)值是數(shù)據(jù)類型的默認(rèn)值(如0、0L、null、false),而不是代碼中被顯示的賦予的值。
  • 如果類字段的字段屬性表中存在ConstatntValue屬性,即同時(shí)被final和static修飾,那么在準(zhǔn)備階段變量value就會(huì)被初始化為ConstValue屬性所指定的值。

4、解析

解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程,解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符7類符號(hào)引用進(jìn)行。符號(hào)引用就是一組符號(hào)來(lái)描述目標(biāo),可以是任何字面量。

直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄。

5、初始化

為類的靜態(tài)變量賦予正確的初始值,JVM負(fù)責(zé)對(duì)類進(jìn)行初始化,主要對(duì)類變量進(jìn)行初始化。初始化階段是執(zhí)行類構(gòu)造器<client>()方法的過程。

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

public class Test{
    static{
        i=0;
        System.out.print(i);
    }
    static int i=1;
}

<clinit>()方法與類構(gòu)造函數(shù)不一樣,不需要顯示調(diào)用父類構(gòu)造函數(shù),虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已執(zhí)行完畢。

由于父類的<clinit>()方法首先執(zhí)行,意味著父類中的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作,如下所示,最終得出的值是2,而不是1。

public class TestClassLoader {
    public static int A = 1;
    static {
        A = 2;
//        System.out.println(A);
    }
    
    static class Sub extends TestClassLoader {
        public static int B = A;
    }
    
    public static void main(String[] args) {
        System.out.println(Sub.B);
    }
}

<clinit>()方法對(duì)于類和接口來(lái)說,并不是必須的,若類沒有靜態(tài)語(yǔ)句塊,也沒有對(duì)變量賦值操作,則不會(huì)生成<clinit>()方法。

接口與類不同的是,接口不需要先執(zhí)行父類的<clinit>()方法,只有父接口定義的變量使用時(shí),父接口才會(huì)被初始化。另外接口的實(shí)現(xiàn)類也不會(huì)先執(zhí)行接口的<clinit>()方法。

虛擬機(jī)保證當(dāng)多線程去初始化類時(shí),只會(huì)有一個(gè)線程去執(zhí)行<clinit>()方法,而其他線程則被阻塞。

<clinit>()方法和<init>()方法區(qū)別:

執(zhí)行時(shí)機(jī)不同:init方法是對(duì)象構(gòu)造器方法,在new一個(gè)對(duì)象并調(diào)用該對(duì)象的constructor方法時(shí)才會(huì)執(zhí)行。clinit方法是類構(gòu)造器方法,是在JVM加載期間的初始化階段才會(huì)調(diào)用。

執(zhí)行目的不同:init是對(duì)非靜態(tài)變量解析初始化,而clinit是對(duì)靜態(tài)變量,靜態(tài)代碼塊進(jìn)行初始化。

三、雙親委派機(jī)制

在介紹雙親委派機(jī)制前,先來(lái)看下類加載器的層次關(guān)系圖,如下:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader),負(fù)責(zé)加載存放在$JAVA_HOME\jre\lib下,或被-Xbootclasspath參數(shù)指定的路徑中的,并且能被虛擬機(jī)識(shí)別的類庫(kù)(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。啟動(dòng)類加載器是無(wú)法被Java程序直接引用的。
  • 擴(kuò)展類加載器(Extension ClassLoader),該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載$JAVA_HOME\jre\lib\ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù)(如javax.*開頭的類),開發(fā)者可以直接使用擴(kuò)展類加載器。
  • 應(yīng)用程序類加載器(Application ClassLoader),該類加載器由sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn),它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
  • 自定義類加載器(User ClassLoader),如果有必要,我們還可以加入自定義的類加載器。因?yàn)镴VM自帶的ClassLoader只是懂得從本地文件系統(tǒng)加載標(biāo)準(zhǔn)的java class文件。

雙親委派機(jī)制是指如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委托給父加載器去完成,依次向上,因此,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時(shí),即無(wú)法完成該加載,子加載器才會(huì)嘗試自己去加載該類。

為了更清楚的了解雙親委派機(jī)制,我們來(lái)看下jdk1.8源碼java.lang.ClassLoader.loadClass()方法實(shí)現(xiàn):

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    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;
        }
    }

上面代碼注釋寫的很清楚,首先調(diào)用findLoadedClass方法檢查是否已加載過這個(gè)類,如果沒有就調(diào)用parent的loadClass方法,從底層一級(jí)級(jí)往上。如果所有ClassLoader都沒有加載過這個(gè)類,就調(diào)用findClass方法查找這個(gè)類,然后又從頂層逐級(jí)向下調(diào)用findClass方法,最終都沒找到就拋出ClassNotFoundException。這樣設(shè)計(jì)的目的是保證安全性,防止系統(tǒng)類被偽造。

為了便于理解,以下是加載邏輯示意圖:

四、自定義類加載器的應(yīng)用

自定義類加載器通常有以下四種應(yīng)用場(chǎng)景:

  • 源代碼加密,防止源碼泄露
  • 隔離加載類,采用隔離加載,防止依賴沖突。
  • 修改類加載的方式。
  • 擴(kuò)展加載源。

1、源代碼加密

源代碼加密的本質(zhì)是對(duì)字節(jié)碼文件進(jìn)行操作。我們可以在打包的時(shí)候?qū)lass進(jìn)行加密操作,然后在加載class文件之前通過自定義classloader先進(jìn)行解密操作,然后再按照標(biāo)準(zhǔn)的class文件標(biāo)準(zhǔn)進(jìn)行加載,這樣就完成了class文件正常的加載。因此這個(gè)加密的jar包只有能夠?qū)崿F(xiàn)解密方法的classloader才能正常加載。

2、隔離加載類

我們常常遇到頭疼的事情就是jar包版本的依賴沖突,寫代碼五分鐘,排包一整天。

舉個(gè)栗子:

工程里面同時(shí)引入了 A、B 兩個(gè) jar 包,以及 C 的 v0.1、v0.2 版本,v2 版本的 Log 類比 v1 版本新增了 error 方法,,打包的時(shí)候 maven 只能選擇 C 的一個(gè)版本,假設(shè)選擇了 v1 版本。到了運(yùn)行的時(shí)候,默認(rèn)情況下一個(gè)項(xiàng)目的所有類都是用同一個(gè)類加載器加載的,所以不管你依賴了多少個(gè)版本的 C,最終只會(huì)有一個(gè)版本的 C 被加載到 JVM 中。當(dāng) B 要去訪問 Log.error,就會(huì)發(fā)現(xiàn) Log 壓根就沒有 error 方法,然后就拋異常 java.lang.NoSuchMethodError。這就是類沖突的一個(gè)典型案例。

類隔離技術(shù)就是用來(lái)解決這個(gè)問題。讓不同模塊的 jar 包用不同的類加載器加載。

JVM 提供了一種非常簡(jiǎn)單有效的方式,我把它稱為類加載傳導(dǎo)規(guī)則:JVM 會(huì)選擇當(dāng)前類的類加載器來(lái)加載所有該類的引用的類。例如我們定義了 TestA 和 TestB 兩個(gè)類,TestA 會(huì)引用 TestB,只要我們使用自定義的類加載器加載 TestA,那么在運(yùn)行時(shí),當(dāng) TestA 調(diào)用到 TestB 的時(shí)候,TestB 也會(huì)被 JVM 使用 TestA 的類加載器加載。依此類推,只要是 TestA 及其引用類關(guān)聯(lián)的所有 jar 包的類都會(huì)被自定義類加載器加載。通過這種方式,我們只要讓模塊的 main 方法類使用不同的類加載器加載,那么每個(gè)模塊的都會(huì)使用 main 方法類的類加載器加載的,這樣就能讓多個(gè)模塊分別使用不同類加載器。這也是 OSGi 和 SofaArk 能夠?qū)崿F(xiàn)類隔離的核心原理。

3、熱加載/熱部署

在應(yīng)用運(yùn)行的時(shí)升級(jí)軟件,無(wú)需重新啟動(dòng)的方式有兩種,熱部署和熱加載。

對(duì)于Java應(yīng)用程序來(lái)說,熱部署就是在服務(wù)器運(yùn)行時(shí)重新部署項(xiàng)目,熱加載即在運(yùn)行時(shí)重新加載class,從而升級(jí)應(yīng)用。

熱加載可以概括為在容器啟動(dòng)的時(shí)候起一條后臺(tái)線程,定時(shí)的檢測(cè)類文件的時(shí)間戳變化,如果類的時(shí)間戳變掉了,則將類重新載入。對(duì)比反射機(jī)制,反射是在運(yùn)行時(shí)獲取類信息,通過動(dòng)態(tài)的調(diào)用來(lái)改變程序行為。而熱加載則是在運(yùn)行時(shí)通過重新加載改變類信息,直接改變程序行為。

熱部署原理類似,但它是直接重新加載整個(gè)應(yīng)用,這種方式會(huì)釋放內(nèi)存,比熱加載更加干凈徹底,但同時(shí)也更費(fèi)時(shí)間。

4、擴(kuò)展加載源

字節(jié)碼文件可以從數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)、移動(dòng)設(shè)備、甚至是電視機(jī)機(jī)頂盒進(jìn)行加載,可以與源代碼加密方式搭配使用。比如部分關(guān)鍵代碼可以通過移動(dòng)U盤讀取再加載到JVM。

到此這篇關(guān)于深入了解Java中的類加載機(jī)制的文章就介紹到這了,更多相關(guān)Java類加載機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java實(shí)現(xiàn)簡(jiǎn)單推箱子游戲

    Java實(shí)現(xiàn)簡(jiǎn)單推箱子游戲

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)推箱子游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • Java提示缺少返回語(yǔ)句的解決辦法

    Java提示缺少返回語(yǔ)句的解決辦法

    在本篇文章里小編給大家分享了關(guān)于Java提示缺少返回語(yǔ)句的解決辦法以及相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。
    2019-07-07
  • Java簡(jiǎn)單工廠模式詳細(xì)解釋

    Java簡(jiǎn)單工廠模式詳細(xì)解釋

    本文主要介紹了JAVA簡(jiǎn)單工廠模式(從現(xiàn)實(shí)生活角度理解代碼原理)的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧
    2021-11-11
  • Java kafka如何實(shí)現(xiàn)自定義分區(qū)類和攔截器

    Java kafka如何實(shí)現(xiàn)自定義分區(qū)類和攔截器

    這篇文章主要介紹了Java kafka如何實(shí)現(xiàn)自定義分區(qū)類和攔截器,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • java讀取文件里面部分漢字內(nèi)容亂碼的解決方案

    java讀取文件里面部分漢字內(nèi)容亂碼的解決方案

    這篇文章主要介紹了java讀取文件里面部分漢字內(nèi)容亂碼的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java解除文件占用即Dom4j操作后實(shí)現(xiàn)xml關(guān)流

    Java解除文件占用即Dom4j操作后實(shí)現(xiàn)xml關(guān)流

    這篇文章主要介紹了Java解除文件占用即Dom4j操作后實(shí)現(xiàn)xml關(guān)流,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • Java Clone深拷貝與淺拷貝的兩種實(shí)現(xiàn)方法

    Java Clone深拷貝與淺拷貝的兩種實(shí)現(xiàn)方法

    今天小編就為大家分享一篇關(guān)于Java Clone深拷貝與淺拷貝的兩種實(shí)現(xiàn)方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-10-10
  • Java中String類常用類型實(shí)例總結(jié)

    Java中String類常用類型實(shí)例總結(jié)

    在我們開發(fā)中經(jīng)常會(huì)用到很多的常用的工具類,這里做一個(gè)總結(jié),下面這篇文章主要給大家介紹了關(guān)于Java中String類常用類型的相關(guān)資料,String類代表字符串,需要的朋友可以參考下
    2021-12-12
  • Java?中不全部使用?Static?方法的理由

    Java?中不全部使用?Static?方法的理由

    這篇文章主要介紹了Java?中不全部使用?Static?方法的理由,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • SpringBoot定時(shí)任務(wù)參數(shù)運(yùn)行代碼實(shí)例解析

    SpringBoot定時(shí)任務(wù)參數(shù)運(yùn)行代碼實(shí)例解析

    這篇文章主要介紹了SpringBoot定時(shí)任務(wù)運(yùn)行代碼實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06

最新評(píng)論