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

          淺談 JVM 4:類加載機(jī)制

          共 2714字,需瀏覽 6分鐘

           ·

          2022-02-18 13:18

          前文中我們了解了如何閱讀字節(jié)碼和字節(jié)碼執(zhí)行引擎中的運(yùn)行時(shí)棧幀結(jié)構(gòu),字節(jié)碼執(zhí)行引擎中的方法調(diào)用等內(nèi)容還未涉及。Java 中多態(tài)的特性離不開 JVM 的動態(tài)綁定,動態(tài)綁定技術(shù)是方法動態(tài)調(diào)用的關(guān)鍵。而如果要介紹方法調(diào)用機(jī)制,需要先了解字節(jié)碼在 JVM 中從加載到卸載的生命周期過程。

          字節(jié)碼在 JVM 中從加載到卸載,經(jīng)歷了以下過程。

          我們目前掌握了如何閱讀外部 class 字節(jié)碼的結(jié)構(gòu),和運(yùn)行時(shí)的部分內(nèi)容。前者屬于 “加載” 之前的內(nèi)容,后者屬于 “使用” 階段。本次我們就來看看從加載到使用之前的這個(gè)過程,這就是 JVM 的類加載機(jī)制。

          外部字節(jié)碼的輸入有多種方式,比如本地 class 文件、網(wǎng)絡(luò)字節(jié)流、程序生成的字節(jié)碼內(nèi)容等。

          Java 虛擬機(jī)將外部的 class 字節(jié)碼加載到內(nèi)存中,需要經(jīng)過加載、鏈接、初始化三個(gè)階段。

          在了解類加載機(jī)制之前,我們先來簡單回顧下 JVM 邏輯區(qū)域的劃分。包括 5 部分:

          ?方法區(qū)?Java 堆?程序計(jì)數(shù)器?Java 棧?本地方法棧

          加載

          加載階段主要負(fù)責(zé)讀取外部的字節(jié)流,將字節(jié)流的存儲結(jié)構(gòu)轉(zhuǎn)化為運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),存儲在方法區(qū)中。同時(shí)在 Java 堆中創(chuàng)建對應(yīng)類的 java.lang.Class 對象,作為方法區(qū)數(shù)據(jù)的訪問入口。此時(shí)方法區(qū)中的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)由虛擬機(jī)各自實(shí)現(xiàn)。

          除了數(shù)組類字節(jié)流由虛擬機(jī)直接生成之外,其他類的加載主要由 ClassLoader 完成。ClassLoader 有啟動類加載器、擴(kuò)展類加載器、應(yīng)用類加載器,其結(jié)構(gòu)符合雙親委派模型,是指子類加載器在加載類時(shí)需要先交給父類加載器加載,如此層層傳遞,如果父類不處理則再交給子類處理。

          啟動類加載器由 C++ 編寫,沒有 Java 對象。擴(kuò)展類加載器、應(yīng)用類加載器等均為 java.lang.ClassLoader 子類,需要由其他類加載器,如啟動類加載器,加載進(jìn)來。

          啟動類加載器主要負(fù)責(zé)加載最為基礎(chǔ)、重要的類,如 JRE 下 lib 目錄中的 jar 包。擴(kuò)展類加載器負(fù)責(zé)加載 JRE 下 lib/ext 目錄中的通用、擴(kuò)展類。應(yīng)用類加載器主要加載應(yīng)用路徑下的類。

          雙親委派模型保證了類加載過程的安全性和唯一性,因?yàn)槿魏巫宇惣虞d器(包括開發(fā)者自己實(shí)現(xiàn)的類加載器)在加載類之前,都需要先將類上交給父類加載器。一方面這保證了不同屬性類的加載功能劃分,比如 Object 類總會交給啟動類加載器加載,這讓無論哪個(gè)類加載器加載 Object,該類都能夠保證相同和唯一。另一方面,這保證了類加載的安全,開發(fā)者無法去偽造諸如 java.lang.Object 等基礎(chǔ)類來騙過 JVM,因?yàn)閮?nèi)置的 ClassLoader 做了相關(guān)安全校驗(yàn)工作。

          需要注意的是,如果同一個(gè)字節(jié)碼交給不同的類加載實(shí)例加載,會得到兩個(gè)不同的類。

          Java 9 模塊化的支持對 ClassLoader 做了些許修改,在此不再詳述。

          鏈接

          鏈接階段又分為驗(yàn)證、準(zhǔn)備、解析三個(gè)階段。

          驗(yàn)證階段

          驗(yàn)證階段主要負(fù)責(zé)驗(yàn)證上一步加載進(jìn)來的字節(jié)碼是否符合規(guī)范,保證不會危害 JVM 的安全。這一步是程序安全的重要保障,此階段的工作也在整個(gè)類加載過程中占有相當(dāng)?shù)谋戎?,主要包括字?jié)碼格式、語法、語義的驗(yàn)證工作。

          準(zhǔn)備階段

          準(zhǔn)備階段主要負(fù)責(zé)為類中的靜態(tài)字段分配內(nèi)存,并初始化為零值。此時(shí)僅會對類變量分配內(nèi)存,在 JDK 8 及之后,類變量內(nèi)存會在 Class 對象所處的 Java 堆中進(jìn)行分配。實(shí)例變量的內(nèi)存分配要等到對象實(shí)例化時(shí)。

          解析階段

          解析階段是將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程。

          在前文我們學(xué)習(xí)閱讀字節(jié)碼時(shí),常量池的常量使用索引表示,常量間的引用關(guān)系、方法字節(jié)碼對變量的獲取等操作都是通過引用索引來完成,這些索引稱為符號引用。符號引用只是表達(dá)變量間的邏輯引用關(guān)系,而實(shí)際運(yùn)行時(shí)變量、方法所處的內(nèi)存位置都不是固定的,所以在運(yùn)行字節(jié)碼前,需要對這些符號引用進(jìn)行解析成指向目標(biāo)的指針、偏移或句柄才行,這和運(yùn)行時(shí)具體的內(nèi)存布局有關(guān)。

          當(dāng)然,解析階段并未完成所有符號引用的解析過程,對于類或接口、字段、類方法、接口方法的解析可以在此階段完成。但是對于方法類型、方法句柄和調(diào)用點(diǎn)限定符的解析則和動態(tài)語言的特性支持密切相關(guān),并不會在此階段完成。這是因?yàn)榉椒ǖ恼{(diào)用具體對應(yīng)哪個(gè)方法在此時(shí)還并未確定,要等到真正執(zhí)行時(shí)才能確定。

          初始化

          初始化階段是虛擬機(jī)開始執(zhí)行應(yīng)用程序代碼的開始,是執(zhí)行類構(gòu)造器??方法的過程。該方法由 Javac 編譯時(shí),通過收集類變量賦值語句和 static 代碼塊合并產(chǎn)生。

          以如下 Java 代碼及其對應(yīng)??字節(jié)碼為例,可以發(fā)現(xiàn)類構(gòu)造器只會處理類變量和靜態(tài)代碼塊中內(nèi)容,而忽略實(shí)例變量。

          // javaprivate static int a = 100;static {    a = 1000;}private int mThisIsInt = 1024;
          // bytecodebipush 100putstatic #28 sipush 1000putstatic #28 return

          在此我們可以提一下靜態(tài)內(nèi)部類單例模式的實(shí)現(xiàn)方式。

          public class Singleton {  private Singleton() {}  private static class Holder {    static final Singleton INSTANCE = new Singleton();  }  public static Singleton getInstance() {    return Holder.INSTANCE;  }}

          內(nèi)部類?Holder?的初始化時(shí)機(jī)是該類的靜態(tài)字段調(diào)用之時(shí),此時(shí)觸發(fā)??方法的調(diào)用,進(jìn)而創(chuàng)建單例對象。JVM 調(diào)用該方法時(shí)會加鎖處理,保證該方法只會調(diào)用一次。該特性保證了單例的延遲加載和多線程安全。

          總結(jié)

          本次我們掌握了 JVM 類加載過程的三個(gè)階段:加載、鏈接、初始化。JVM 在邏輯上分為方法區(qū)、Java 堆、程序計(jì)數(shù)器、Java 棧、本地方法棧。方法區(qū)和 Java 堆主要用于存儲數(shù)據(jù),包括字節(jié)碼對應(yīng)數(shù)據(jù)結(jié)構(gòu)和運(yùn)行時(shí)的對象。其他部分則主要負(fù)責(zé)運(yùn)行時(shí)方法調(diào)用、計(jì)算等功能。

          在了解了類加載機(jī)制、JVM 邏輯區(qū)域劃分和運(yùn)行時(shí)棧幀及方法字節(jié)碼執(zhí)行過程之后,下次有時(shí)間我們來聊聊方法間的調(diào)用是如何發(fā)生的。


          瀏覽 40
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  欧洲一区二区在线 | 亚洲91成人电影 | 国产成人无码综合亚洲日韩不卡 | 日本A V中文字幕 | 人人妻人人澡人人爽人人 |