<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          虛擬機(jī)類(lèi)加載機(jī)制

          共 5483字,需瀏覽 11分鐘

           ·

          2020-11-24 13:22

          PS:雖然最近更新頻率低了,但是思危一直沒(méi)有停止,共勉!
          今天介紹一下 JVM 類(lèi)加載器機(jī)制,主要內(nèi)容如下:
          1. 概述

          2. 類(lèi)加載的時(shí)機(jī)

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

          4. 類(lèi)加載器

          5. 類(lèi)加載器分類(lèi)

          6. 雙親委托模型

          概述

          JVM 把字節(jié)碼(.class)文件加載到內(nèi)存中,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、解析和初始化,最終生成可以被 JVM 直接使用的 Java 類(lèi)型,這就是 JVM 的類(lèi)加載機(jī)制。
          在 Java 中各種類(lèi)型的加載、連接和初始化過(guò)程都是在程序運(yùn)行期間完成的,這種方式會(huì)在類(lèi)加載時(shí)帶來(lái)一些性能開(kāi)銷(xiāo),但是具有很高的靈活性,Java 的動(dòng)態(tài)擴(kuò)展的語(yǔ)言特性就是依賴(lài)運(yùn)行期間動(dòng)態(tài)加載和動(dòng)態(tài)鏈接這個(gè)特點(diǎn)實(shí)現(xiàn)的,如插件化技術(shù)中通過(guò)自定義類(lèi)加載器實(shí)現(xiàn)資源的加載和替換,其中就是用的 Java 語(yǔ)言運(yùn)行期間類(lèi)加載的特性。

          類(lèi)加載的時(shí)機(jī)

          類(lèi)從被加載到 JVM 內(nèi)存中開(kāi)始,一直到從 JVM 內(nèi)存中卸載為止,類(lèi)加載的生命周期如下圖所示:
          加載、驗(yàn)證、準(zhǔn)備、初始化、卸載這五個(gè)階段的順序是確定的,類(lèi)的解析則不一定,可能會(huì)在初始化之后再進(jìn)行,這是為了支持 Java 語(yǔ)言的運(yùn)行時(shí)綁定,在整個(gè)類(lèi)加載的過(guò)程中,每一個(gè)階段都由前一個(gè)階段觸發(fā)進(jìn)行。
          JVM 規(guī)范中規(guī)定了類(lèi)的初始化階段,但是加載這個(gè)階段沒(méi)有進(jìn)行約束,具體由 JVM 實(shí)現(xiàn)自己控制,當(dāng)然加載、驗(yàn)證、準(zhǔn)備必須在初始化這個(gè)階段之前完成。
          那么什么情況下類(lèi)開(kāi)始初始化呢,JVM 嚴(yán)格規(guī)定了下面這些情況必須對(duì)類(lèi)進(jìn)行初始化:
          1. 遇到 new、getstatic/putstatic、invokestatic 指令時(shí),如果該類(lèi)沒(méi)有被初始化,則需對(duì)類(lèi)進(jìn)行初始化,上面指令分別對(duì)應(yīng)使用 new 關(guān)鍵字進(jìn)行對(duì)象實(shí)例化、讀取或設(shè)置一個(gè)靜態(tài)屬性、調(diào)用靜態(tài)方法,具體可以使用 javap 命令查看字節(jié)碼文件的實(shí)現(xiàn)來(lái)驗(yàn)證;

          2. 使用 java,lang.reflect 對(duì)類(lèi)進(jìn)行反射調(diào)用的時(shí)候,如果該類(lèi)沒(méi)有被初始化,則需對(duì)類(lèi)進(jìn)行初始化;

          3. 當(dāng)初始化一個(gè)類(lèi)的時(shí)候,如果其父類(lèi)還沒(méi)有進(jìn)行初始化,則先進(jìn)行該類(lèi)父類(lèi)的初始化;

          4. 當(dāng) JVM 啟動(dòng)時(shí),用戶(hù)指定要啟動(dòng)的主類(lèi),比如還有 main 方法的類(lèi),JVM 會(huì)先初始化這個(gè)類(lèi);

          5. 當(dāng)使用 JDK 1.7 的動(dòng)態(tài)語(yǔ)言支持時(shí),如果 java.lang.invoke.MethodHandler 實(shí)例最后解析結(jié)果是 REF_getStatic、REF_putStatic、REF_invokeStatic,如果這些句柄對(duì)應(yīng)的類(lèi)沒(méi)有進(jìn)行初始化,則需先對(duì)其進(jìn)行初始化,至于 MethodHandler ?可以理解為反射的另一種形式。

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

          下面針對(duì)類(lèi)加載的幾個(gè)階段進(jìn)行具體說(shuō)明。
          加載
          class 文件通過(guò)類(lèi)加載器將其中的字節(jié)碼內(nèi)容加載到內(nèi)存中,將這個(gè)靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),在堆內(nèi)存中生成該 class 文件對(duì)應(yīng)的 java.lang.Class 對(duì)象,這個(gè) Class 對(duì)象就是訪(fǎng)問(wèn)方法區(qū)類(lèi)數(shù)據(jù)的入口。
          JVM 規(guī)范并沒(méi)有規(guī)定 class 文件的來(lái)源,舉例如下:
          • 從 zip 包中獲取,最終成為 jar、war 格式的基礎(chǔ)。

          • 從網(wǎng)絡(luò)中獲取,典型應(yīng)用就是 Applet。

          • 運(yùn)行時(shí)生成,典型應(yīng)用就是動(dòng)態(tài)代理技術(shù),在 java.lang.reflect.Proxy 中就是使用 ProxyGenerator.generatrProxyClass 來(lái)為特定接口生成形如 Proxy 的代理類(lèi)的二進(jìn)制字節(jié)流。

          • 其他文件生成、數(shù)據(jù)庫(kù)中獲取等。

          類(lèi)的加載階段與后面的鏈接階段的過(guò)程是交叉進(jìn)行的,沒(méi)有明確的界限,加載階段尚未完成,鏈接階段可能已經(jīng)開(kāi)始,但是兩個(gè)階段的開(kāi)始時(shí)間還是保持著固定的先后順序。
          鏈接
          鏈接包括驗(yàn)證、準(zhǔn)備、解析三個(gè)階段,
          • 驗(yàn)證:確保 class 文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,且不會(huì)危害虛擬機(jī)自身的安全,從整體來(lái)看,驗(yàn)證階段主要包括文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證,具體驗(yàn)證內(nèi)容可以自行查看 Java 虛擬機(jī)規(guī)范。

          • 準(zhǔn)備:正式為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量的初始值,這個(gè)初始值一般是數(shù)據(jù)類(lèi)型的初始值,而不是真正代碼中初始化的值,如 int 初始值就是 0,這些類(lèi)變量使用的內(nèi)存都將在方法區(qū)進(jìn)行分配,類(lèi)變量指的就是被 static 關(guān)鍵字修飾過(guò)的變量。

          • 解析:JVM 將常量池中的符號(hào)引用替換為直接引用,這里的符號(hào)引用就是在前面驗(yàn)證階段提到的符號(hào)引用驗(yàn)證中的符號(hào)引用

          初始化
          類(lèi)初始化階段是類(lèi)加載階段的最后一步,前面的加載階段、鏈接階段除了用戶(hù)自定義類(lèi)加載其參與外,其余操作都是由 JVM 來(lái)完成的,初始化階段才真正開(kāi)始執(zhí)行 Java 代碼,也就是字節(jié)碼,關(guān)于類(lèi)的初始化可以了解一下幾點(diǎn):
          1. 初始化階段就是執(zhí)行類(lèi)構(gòu)造器 () 方法的過(guò)程。

          2. <clinit>() 方法是由編譯器自動(dòng)收集類(lèi)中的所有類(lèi)變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊 statuc{} 中的語(yǔ)句合并產(chǎn)生的,編譯器收集順序和源碼中語(yǔ)句順序一致,如靜態(tài)語(yǔ)句塊中只能訪(fǎng)問(wèn)定義在它之前的變量,定義在它后面的變量只能復(fù)制不能訪(fǎng)問(wèn)。
          3. 初始化一個(gè)類(lèi)時(shí),如果父類(lèi)還沒(méi)初始化,則先進(jìn)行父類(lèi)的初始化。

          4. JVM 會(huì)保證一個(gè)類(lèi)的 () 方法在多線(xiàn)程環(huán)境中被正確的加鎖、同步。

          5. 當(dāng)訪(fǎng)問(wèn)一個(gè) Java 類(lèi)的靜態(tài)域時(shí),只有真正聲明這個(gè)類(lèi)才會(huì)被初始化。

          類(lèi)加載器

          顧名思義,類(lèi)加載器(class loader) 用來(lái)加載 Java 類(lèi)到 JVM 中的,所有的類(lèi)加載器都是 java.lang.ClassLoader 類(lèi)的一個(gè)實(shí)例,前面知道在類(lèi)的加載階段會(huì)通過(guò)類(lèi)加載器來(lái)加載 class 文件,也就是可以通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二進(jìn)制字節(jié)流 ,這個(gè)動(dòng)作的代碼實(shí)現(xiàn)就是類(lèi)加載器的實(shí)現(xiàn)。
          對(duì)于任意一個(gè)類(lèi),都需要加載它的類(lèi)加載器和這個(gè)類(lèi)本身一同確立其在 JVM 中的唯一性,每個(gè)類(lèi)加載器都擁有獨(dú)立的類(lèi)名稱(chēng)空間,也就是說(shuō),兩個(gè)相同的類(lèi)被不同的類(lèi)加載器加載后將不再相等。

          類(lèi)加載器分類(lèi)

          從 JVM 的角度來(lái)說(shuō),只存在兩種不同的類(lèi)加載器:
          1. 啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):一般使用 C++ 語(yǔ)言實(shí)現(xiàn),具體由 JVM 實(shí)現(xiàn)。

          2. 其他類(lèi)加載器:使用 Java 語(yǔ)言實(shí)現(xiàn),獨(dú)立于 JVM 之外,且都是 java.lang.ClassLoader 的一個(gè)實(shí)例,如 Android 中的 DexClassLoader。

          從 Java 開(kāi)發(fā)人員的角度來(lái)說(shuō),類(lèi)加載器可以分為三類(lèi):
          1. 啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):負(fù)責(zé)加載的是 JAVA_HOME\lib 下的類(lèi)庫(kù),或者被-Xbootclasspath 參數(shù)所指定的路徑中的并且是 JVM 識(shí)別的(僅按照文件名識(shí)別,如 rt.jar 等,名字不符合的類(lèi)庫(kù)即使放在 lib 目錄中也不會(huì)被加載),啟動(dòng)類(lèi)加載器無(wú)法被JAVA程序直接應(yīng)用。

          2. 擴(kuò)展類(lèi)加載器(Extension ClassLoader):這個(gè)類(lèi)加載器由 sum.misc.Launcher$ExtClassLoader 實(shí)現(xiàn),負(fù)責(zé)加載 JAVA_HOME\lib\ext 下的類(lèi),或者是被 java.ext.dirs 系統(tǒng)變量所指定的路徑下的類(lèi)庫(kù),可以直接使用擴(kuò)展類(lèi)加載器。

          3. 應(yīng)用程序類(lèi)加載器(Application ClassLoader):這個(gè)類(lèi)加載器由 sun.misc.Launcher$AppClassLoader 實(shí)現(xiàn),這個(gè)類(lèi)加載器是 ClassLoader 中的getSystemClassLoader() 方法的返回值, 一般也稱(chēng)它為系統(tǒng)類(lèi)加載器,負(fù)責(zé)加載用戶(hù)類(lèi)路徑(ClassPath) 下所指定的類(lèi)庫(kù), 開(kāi)發(fā)者可以直接使用這個(gè)類(lèi)加載器, 如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器, 這個(gè)就是程序中默認(rèn)的類(lèi)加載器。

          雙親委托模型

          先來(lái)看一下上面類(lèi)加載器的關(guān)系:
          上圖中展示的類(lèi)加載器之間的這種層次關(guān)系, 稱(chēng)為類(lèi)加載器的雙親委派模型( Parents Delegation Model),雙親委派模型要求除了頂層的啟動(dòng)類(lèi)加載器外, 其余的類(lèi)加載器都應(yīng)當(dāng)有自己的父類(lèi)加載器, 這里類(lèi)加載器之間的父子關(guān)系一般不會(huì)以繼承(Inheritance) 的關(guān)系
          來(lái)實(shí)現(xiàn), 而是都使用組合(Composition) 關(guān)系來(lái)復(fù)用父加載器的代碼,這種方式并不是一個(gè)強(qiáng)制性的約束模型, 而是 Java 設(shè)計(jì)者推薦給開(kāi)發(fā)者的一種類(lèi)加載器實(shí)現(xiàn)方式。
          那么雙親委托模型的工作流程是什么呢?
          當(dāng)一個(gè)類(lèi)加載器收到類(lèi)加載的請(qǐng)求,首先不會(huì)去加載這個(gè)類(lèi),而是把這個(gè)類(lèi)加載的請(qǐng)求委托給父類(lèi)類(lèi)加載器,以此類(lèi)推,最終每個(gè)類(lèi)加載的請(qǐng)求都會(huì)委托給啟動(dòng)類(lèi)加載器,只有當(dāng)父類(lèi)類(lèi)加載器無(wú)法完成該類(lèi)的加載,子類(lèi)加載器才會(huì)嘗試自己去加載,加載過(guò)程參考如下:
          1protected?Class?loadClass(String?name,?boolean?resolve)throws?ClassNotFoundException{
          2????synchronized?(getClassLoadingLock(name))?{
          3????????//?1.?檢查是否已經(jīng)加載過(guò)類(lèi)
          4????????Class?c?=?findLoadedClass(name);
          5????????if?(c?==?null)?{
          6????????????long?t0?=?System.nanoTime();
          7????????????try?{
          8????????????????if?(parent?!=?null)?{
          9????????????????????//?2.?如果沒(méi)有被加載,則調(diào)用父類(lèi)的類(lèi)加載器加載
          10????????????????????c?=?parent.loadClass(name,?false);
          11????????????????}?else?{
          12????????????????????//?3.?如果父類(lèi)的類(lèi)加載器不存在,則直接使用啟動(dòng)類(lèi)加載器進(jìn)行加載
          13????????????????????c?=?findBootstrapClassOrNull(name);
          14????????????????}
          15????????????}?catch?(ClassNotFoundException?e)?{
          16????????????????//?ClassNotFoundException?thrown?if?class?not?found
          17????????????????//?from?the?non-null?parent?class?loader
          18????????????}
          19
          20????????????if?(c?==?null)?{
          21????????????????//?If?still?not?found,?then?invoke?findClass?in?order
          22????????????????//?to?find?the?class.
          23????????????????long?t1?=?System.nanoTime();
          24????????????????//?4.?父類(lèi)或啟動(dòng)類(lèi)加載器都沒(méi)有加載該類(lèi),則調(diào)用自己的也就是子類(lèi)類(lèi)加載器的findClass犯法進(jìn)行類(lèi)加載
          25????????????????c?=?findClass(name);
          26????????????????sun.misc.PerfCounter.getParentDelegationTime().addTime(t1?-?t0);
          27????????????????sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
          28????????????????sun.misc.PerfCounter.getFindClasses().increment();
          29????????????}
          30????????}
          31????????if?(resolve)?{
          32????????????resolveClass(c);
          33????????}
          34????????return?c;
          35????}
          36}
          JDK 1.2 之后的 java.lang.ClassLoader 添加了一個(gè)新的 protected 方法 findClass() ,如果要自定義類(lèi)加載器,則直接實(shí)現(xiàn) findClass() 方法即可而不必重寫(xiě) ?loadClass() 方法,因?yàn)?loadClass() 方法最終調(diào)用了 findClass() 方法,這樣自定義的類(lèi)加載器就是符合雙親委托規(guī)則的。
          前面介紹了 JVM 類(lèi)加載機(jī)制以及類(lèi)加載器的相關(guān)知識(shí),類(lèi)加載器很好的支持了 Java 的動(dòng)態(tài)擴(kuò)展特性,在 Android 中也有使用,如在插件化技術(shù)中用到的 PathClassLoader、DexClassLoader 都是 ClassLoader 的間接子類(lèi),正是這種對(duì) class 文件來(lái)源沒(méi)有進(jìn)行限制,基于此可以實(shí)現(xiàn) App 的插件化。
          推薦閱讀:
          瀏覽 45
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  丁香激情五月 | 黄色视频亚洲在线免费观看 | 青草无码视频 | 柠檬福利第一导航在线 | 99这里只有精品国产 |