1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        今天我們來(lái)聊聊JVM類(lèi)加載機(jī)制

        共 6244字,需瀏覽 13分鐘

         ·

        2021-03-09 17:00

        hello 我是寶哥,今天我們來(lái)聊聊JVM的類(lèi)加載過(guò)程

        要搞清楚JVM,首先要搞清楚幾個(gè)問(wèn)題:

        • jvm起到什么作用?
        • 怎么加載class文件的?
        • 加載類(lèi)時(shí)會(huì)重復(fù)嗎?順序是什么樣的?

        說(shuō)到j(luò)vm 那么不得不提類(lèi)的加載過(guò)程.我們先來(lái)了解下類(lèi)是如何被一步一步加載到j(luò)vm的

        類(lèi)的加載過(guò)程

        我們先籠統(tǒng)的了解一下類(lèi)加載的整個(gè)過(guò)程:

        如上圖所示,Java源代碼文件(.java后綴)會(huì)被Java編譯器編譯為字節(jié)碼文件(.class后綴)

        然后由JVM中的類(lèi)加載器加載各個(gè)類(lèi)的字節(jié)碼文件,加載完畢之后,交由JVM執(zhí)行引擎執(zhí)行。

        在整個(gè)程序執(zhí)行過(guò)程中,JVM用一段空間來(lái)存儲(chǔ)程序執(zhí)行期間需要用到的數(shù)據(jù)和相關(guān)信息,被稱(chēng)作為Runtime Data Area(運(yùn)行時(shí)數(shù)據(jù)區(qū)),也就是我們常說(shuō)的JVM內(nèi)存。

        此時(shí),我們可以片面的理解為:JVM為我們的class字節(jié)碼提供了加載,存儲(chǔ),執(zhí)行的環(huán)境.(jvm是java可跨平臺(tái)運(yùn)行的基石,因?yàn)椴煌南到y(tǒng)有不同的jvm實(shí)現(xiàn),都可以加載.class字節(jié)碼文件)

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

        那么ClassLoader都做了什么呢?

        我們先來(lái)看看 類(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ī)制

        這里有幾個(gè)階段比較重要: 1.加載  2.連接  3.初始化

        根據(jù)這3個(gè)階段,我們可以剖析出,類(lèi)的生命周期:

        類(lèi)的生命周期

        • 加載: 加載類(lèi)的二進(jìn)制字節(jié)流,并且將靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),然后在內(nèi)存中生成一個(gè)代表此類(lèi)的Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)各種數(shù)據(jù)的訪問(wèn)入口

        • 驗(yàn)證: 驗(yàn)證是在連接(Linking)部分的第一步,驗(yàn)證的目的是驗(yàn)證Class文件中的字節(jié)流符合當(dāng)前虛擬機(jī)的要求,保證不會(huì)危害虛擬機(jī).

        • 準(zhǔn)備: 為類(lèi)變量分配內(nèi)存,并且設(shè)置類(lèi)變量初始值,此時(shí)這此類(lèi)變量所使用的內(nèi)存都是在方法區(qū)中進(jìn)行分配.

        • 解析: 解析是將符號(hào)引用替換為直接引用,解析動(dòng)作針對(duì)類(lèi)或接口,字段,類(lèi)或接口的方法進(jìn)行解析。

        • 初始化:初始化類(lèi)或接口并且執(zhí)行類(lèi)或接口的初始化方法,此時(shí),它的生命周期就開(kāi)始了

          • 虛擬機(jī)規(guī)范規(guī)定了有且只有 5 種情況必須立即對(duì)類(lèi)進(jìn)行初始化
          • 1.遇到new、getstatic 和 putstatic 或 invokestatic 這4條字節(jié)碼指令時(shí),如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。對(duì)應(yīng)場(chǎng)景是:使用 new 實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類(lèi)的靜態(tài)字段(被 final 修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、以及調(diào)用一個(gè)類(lèi)的靜態(tài)方法。
          • 2.對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候,如果類(lèi)沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。
          • 3.當(dāng)初始化類(lèi)的父類(lèi)還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類(lèi)的初始化。(而一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化)
          • 4.虛擬機(jī)啟動(dòng)時(shí),用戶(hù)需要指定一個(gè)要執(zhí)行的主類(lèi)(包含 main() 方法的那個(gè)類(lèi)),虛擬機(jī)會(huì)先初始化這個(gè)主類(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ā)其初始化。
          • 初始化的時(shí)機(jī):
        • 使用: 此時(shí)我們可以通過(guò)new關(guān)鍵字,創(chuàng)建實(shí)例對(duì)象.順帶一提,一個(gè)Class對(duì)象總是會(huì)引用它的類(lèi)加載器。調(diào)用Class對(duì)象的getClassLoader()方法,就能獲得它的類(lèi)加載器。由此可見(jiàn),Class實(shí)例和加載它的加載器之間為雙向關(guān)聯(lián)關(guān)系。

        • 卸載: 當(dāng)類(lèi)不再被引用或被垃圾回收器標(biāo)記為已死對(duì)象時(shí),將會(huì)被回收,但是Java虛擬機(jī)本身會(huì)始終引用類(lèi)加載器,而這些類(lèi)加載器則會(huì)始終引用它們所加載的類(lèi)的Class對(duì)象,因此這些Class對(duì)象始終是可觸及的,也就是說(shuō)jvm自帶的類(lèi)加載器所加載的類(lèi),在虛擬機(jī)還沒(méi)有退出時(shí),始終不會(huì)被卸載,當(dāng)然也有特例 如:我們自己定義的類(lèi)加載器的類(lèi)是可以被卸載的.

        ClassLoader 類(lèi)加載器

        類(lèi)的唯一性

        任意一個(gè)類(lèi),都需要由加載它的類(lèi)加載器和這個(gè)類(lèi)本身一同確立其在Java虛擬機(jī)中的唯一性

        這句定義怎么理解呢?

        兩個(gè)類(lèi)來(lái)源于同一個(gè) Class 文件,被同一個(gè)虛擬機(jī)加載,但是加載它們的類(lèi)加載器不同,那這兩個(gè)類(lèi)也不相等

        那有的小伙伴就有疑惑了,還有很多類(lèi)加載器嗎? emm..那加載的順序呢?會(huì)不會(huì)重復(fù)加載了?

        別急,我們了解下雙親委派原則.

        雙親委派原則

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

        這里舉例幾個(gè)面試會(huì)問(wèn)的classloader職責(zé):

        • Bootstrap ClassLoader:根類(lèi)加載器,負(fù)責(zé)加載java的核心類(lèi),它不是java.lang.ClassLoader的子類(lèi),而是由JVM自身實(shí)現(xiàn);
        • Extension ClassLoader:擴(kuò)展類(lèi)加載器,擴(kuò)展類(lèi)加載器的加載路徑是JDK目錄下jre/lib/ext,擴(kuò)展類(lèi)的getParent()方法返回null,實(shí)際上擴(kuò)展類(lèi)加載器的父類(lèi)加載器是根加載器,只是根加載器并不是Java實(shí)現(xiàn)的;
        • System ClassLoader:系統(tǒng)(應(yīng)用)類(lèi)加載器,它負(fù)責(zé)在JVM啟動(dòng)時(shí)加載來(lái)自java命令的-classpath選項(xiàng)、java.class.path系統(tǒng)屬性或CLASSPATH環(huán)境變量所指定的jar包和類(lèi)路徑。程序可以通過(guò)getSystemClassLoader()來(lái)獲取系統(tǒng)類(lèi)加載器;
        • User Define ClassLoader: 用戶(hù)自定義的classloader,自定義的加載器必須繼承ClassLoader。

        它們的加載順序:

        show me the code:

        // 代碼摘自《深入理解Java虛擬機(jī)》
            protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
                // 首先,檢查請(qǐng)求的類(lèi)是否已經(jīng)被加載過(guò)了,同時(shí)也解決了小伙伴的疑慮
                Class c = findLoadedClass(name);
                if (c == null) {
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                    // 如果父類(lèi)加載器拋出ClassNotFoundException
                    // 說(shuō)明父類(lèi)加載器無(wú)法完成加載請(qǐng)求
                    }
                    if (c == null) {
                        // 在父類(lèi)加載器無(wú)法加載的時(shí)候
                        // 再調(diào)用本身的findClass方法來(lái)進(jìn)行類(lèi)加載
                        c = findClass(name);
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }

        那么我們不禁要思考一下為何要用這種原則有何優(yōu)點(diǎn)?

        • 1,一個(gè)當(dāng)然是避免重復(fù)加載,提升性能

        • 2,避免了核心類(lèi)被用戶(hù)篡改(例如我在用戶(hù)自定義的classloader中加載一個(gè)String類(lèi)去覆蓋自帶的String類(lèi),由于先讓父類(lèi)加載,我定義的順序在后.不會(huì)出現(xiàn)覆蓋成功的問(wèn)題)

        這里有幾個(gè)點(diǎn)小伙伴需要注意,不然要被面試官吊打了:

        1. 類(lèi)加載器之間的父子關(guān)系不會(huì)以繼承的關(guān)系來(lái)實(shí)現(xiàn),他們雖然都繼承于抽象類(lèi)java.lang.ClassLoader但是他們的關(guān)系是組合關(guān)系,使用組合關(guān)系復(fù)用父類(lèi)的加載器.
        2. Bootstrap 類(lèi)加載器是用 C++ 實(shí)現(xiàn)的,不繼承于抽象類(lèi)java.lang.ClassLoader,它是虛擬機(jī)自身的一部分,如果獲取它的對(duì)象,將會(huì)返回 null;
        3. jvm自帶的類(lèi)加載器所加載的類(lèi),在虛擬機(jī)還沒(méi)有退出時(shí),始終不會(huì)被卸載,我們自己定義的類(lèi)加載器,加載的類(lèi)是可以被卸載的.

        破壞雙親委派原則

        當(dāng)然類(lèi)加載器的雙親委派原則是可以被破壞的,破壞它是由于雙親委派模型自身缺陷導(dǎo)致,他沒(méi)有辦法解決用戶(hù)基礎(chǔ)類(lèi)又要重新調(diào)用戶(hù)類(lèi)的代碼。為了解決這個(gè)問(wèn)題就有了線程上下文加載器,例如JNDI、JDBC、JCE等

        舉個(gè)Tomcat的例子:

        每個(gè)Tomcat的webappClassLoader加載自己的目錄下的class文件,不會(huì)傳遞給父類(lèi)加載器。tomcat之所以造了一堆自己的classloader,大致是出于下面三個(gè)原因:

        • 對(duì)于各個(gè) webapp中的 class和 lib,需要相互隔離,不能出現(xiàn)一個(gè)應(yīng)用中加載的類(lèi)庫(kù)會(huì)影響另一個(gè)應(yīng)用的情況,而對(duì)于許多應(yīng)用,需要有共享的lib以便不浪費(fèi)資源。
        • 與 jvm一樣的安全性問(wèn)題。使用單獨(dú)的 classloader去裝載 tomcat自身的類(lèi)庫(kù),以免其他惡意或無(wú)意的破壞;
        • 熱部署。相信大家一定為 tomcat修改文件不用重啟就自動(dòng)重新裝載類(lèi)庫(kù)而驚嘆吧。

        破壞雙親委派的方式

        雙親委派機(jī)制原則在loadclass方法中。只需要繞開(kāi)loadclass方法中即可。

        1. 自定義類(lèi)加載器 ,重寫(xiě)loadclass方法

        2. 使用SPI機(jī)制繞開(kāi)loadclass 方法。當(dāng)前線程設(shè)定關(guān)聯(lián)類(lèi)加載器

        關(guān)于SPI在我另外一篇文章https://mp.weixin.qq.com/s/2UFHJ_i09APy-VS8oeiUIQ


        講了那么久的加載,此時(shí)我們才剛剛一只腳踏進(jìn)JVM的大門(mén),后面我們將分析JVM運(yùn)行時(shí)數(shù)據(jù)區(qū).大家持續(xù)關(guān)注java寶典公眾號(hào),我們下章再聊

        最近我創(chuàng)建了一個(gè)學(xué)習(xí)群,有熱愛(ài)學(xué)習(xí)的小伙伴可以進(jìn)群討論~


        JAVA的SPI機(jī)制


        JVM生成的這3種文件,你都見(jiàn)過(guò)嗎?




        瀏覽 48
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            aa人人操夜夜操人人| 亚洲无线观看| 91羞射短视频在线观看| 精品视频网站| 日韩欧美国产精品| 成人毛片网站| 日韩国产成人在线| 日韩精品免费在线观看| 成人特级毛片| 中文字幕三级片在线观看|