小編給大家分享一下Java的類加載機(jī)制是什么,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到長(zhǎng)清網(wǎng)站設(shè)計(jì)與長(zhǎng)清網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名注冊(cè)、網(wǎng)頁(yè)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋長(zhǎng)清地區(qū)。
01、字節(jié)碼
在聊 Java 類加載機(jī)制之前,需要先了解一下 Java 字節(jié)碼,因?yàn)樗皖惣虞d機(jī)制息息相關(guān)。
計(jì)算機(jī)只認(rèn)識(shí) 0 和 1,所以任何語(yǔ)言編寫(xiě)的程序都需要編譯成機(jī)器碼才能被計(jì)算機(jī)理解,然后執(zhí)行,Java 也不例外。
Java 在誕生的時(shí)候喊出了一個(gè)非常牛逼的口號(hào):“Write Once, Run Anywhere”,為了達(dá)成這個(gè)目的,Sun 公司發(fā)布了許多可以在不同平臺(tái)(Windows、Linux)上運(yùn)行的 Java 虛擬機(jī)(JVM)——負(fù)責(zé)載入和執(zhí)行 Java 編譯后的字節(jié)碼。
到底 Java 字節(jié)碼是什么樣子,我們借助一段簡(jiǎn)單的代碼來(lái)看一看。
源碼如下:
package com.cmower.java_demo; public class Test { public static void main(String[] args) { System.out.println("版權(quán)聲明"); } }
代碼編譯通過(guò)后,通過(guò) xxd Test.class 命令查看一下這個(gè)字節(jié)碼文件。
xxd Test.class 00000000: cafe babe 0000 0034 0022 0700 0201 0019 .......4."...... 00000010: 636f 6d2f 636d 6f77 6572 2f6a 6176 615f com/cmower/java_ 00000020: 6465 6d6f 2f54 6573 7407 0004 0100 106a demo/Test......j 00000030: 6176 612f 6c61 6e67 2f4f 626a 6563 7401 ava/lang/Object. 00000040: 0006 3c69 6e69 743e 0100 0328 2956 0100 .....()V.. 00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601 .Code........... 00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c ..LineNumberTabl
感覺(jué)有點(diǎn)懵逼,對(duì)不對(duì)?
懵就對(duì)了。
這段字節(jié)碼中的 cafe babe 被稱為“魔數(shù)”,是 JVM 識(shí)別 .class 文件的標(biāo)志。文件格式的定制者可以自由選擇魔數(shù)值(只要沒(méi)用過(guò)),比如說(shuō) .png 文件的魔數(shù)是 8950 4e47。
至于其他內(nèi)容嘛,可以選擇忘記了。
02、類加載過(guò)程
了解了 Java 字節(jié)碼后,我們來(lái)聊聊 Java 的類加載過(guò)程。
Java 的類加載過(guò)程可以分為 5 個(gè)階段:載入、驗(yàn)證、準(zhǔn)備、解析和初始化。這 5 個(gè)階段一般是順序發(fā)生的,但在動(dòng)態(tài)綁定的情況下,解析階段發(fā)生在初始化階段之后。
1)Loading(載入)
JVM 在該階段的主要目的是將字節(jié)碼從不同的數(shù)據(jù)源(可能是 class 文件、也可能是 jar 包,甚至網(wǎng)絡(luò))轉(zhuǎn)化為二進(jìn)制字節(jié)流加載到內(nèi)存中,并生成一個(gè)代表該類的 java.lang.Class 對(duì)象。
2)Verification(驗(yàn)證)
JVM 會(huì)在該階段對(duì)二進(jìn)制字節(jié)流進(jìn)行校驗(yàn),只有符合 JVM 字節(jié)碼規(guī)范的才能被 JVM 正確執(zhí)行。該階段是保證 JVM 安全的重要屏障,下面是一些主要的檢查。
確保二進(jìn)制字節(jié)流格式符合預(yù)期(比如說(shuō)是否以 cafe bene 開(kāi)頭)。
是否所有方法都遵守訪問(wèn)控制關(guān)鍵字的限定。
方法調(diào)用的參數(shù)個(gè)數(shù)和類型是否正確。
確保變量在使用之前被正確初始化了。
檢查變量是否被賦予恰當(dāng)類型的值。
3)Preparation(準(zhǔn)備)
JVM 會(huì)在該階段對(duì)類變量(也稱為靜態(tài)變量,static 關(guān)鍵字修飾的)分配內(nèi)存并初始化(對(duì)應(yīng)數(shù)據(jù)類型的默認(rèn)初始值,如 0、0L、null、false 等)。
也就是說(shuō),假如有這樣一段代碼:
public String chenmo = "沉默"; public static String wanger = "王二"; public static final String cmower = "沉默王二";
chenmo 不會(huì)被分配內(nèi)存,而 wanger 會(huì);但 wanger 的初始值不是“王二”而是 null。
需要注意的是,static final 修飾的變量被稱作為常量,和類變量不同。常量一旦賦值就不會(huì)改變了,所以 cmower 在準(zhǔn)備階段的值為“沉默王二”而不是 null。
4)Resolution(解析)
該階段將常量池中的符號(hào)引用轉(zhuǎn)化為直接引用。
what?符號(hào)引用,直接引用?
符號(hào)引用以一組符號(hào)(任何形式的字面量,只要在使用時(shí)能夠無(wú)歧義的定位到目標(biāo)即可)來(lái)描述所引用的目標(biāo)。
在編譯時(shí),Java 類并不知道所引用的類的實(shí)際地址,因此只能使用符號(hào)引用來(lái)代替。比如 com.Wanger 類引用了 com.Chenmo 類,編譯時(shí) Wanger 類并不知道 Chenmo 類的實(shí)際內(nèi)存地址,因此只能使用符號(hào) com.Chenmo。
直接引用通過(guò)對(duì)符號(hào)引用進(jìn)行解析,找到引用的實(shí)際內(nèi)存地址。
5)Initialization(初始化)
該階段是類加載過(guò)程的最后一步。在準(zhǔn)備階段,類變量已經(jīng)被賦過(guò)默認(rèn)初始值,而在初始化階段,類變量將被賦值為代碼期望賦的值。換句話說(shuō),初始化階段是執(zhí)行類構(gòu)造器方法的過(guò)程。
oh,no,上面這段話說(shuō)得很抽象,不好理解,對(duì)不對(duì),我來(lái)舉個(gè)例子。
String cmower = new String("沉默王二");
上面這段代碼使用了 new 關(guān)鍵字來(lái)實(shí)例化一個(gè)字符串對(duì)象,那么這時(shí)候,就會(huì)調(diào)用 String 類的構(gòu)造方法對(duì) cmower 進(jìn)行實(shí)例化。
03、類加載器
聊完類加載過(guò)程,就不得不聊聊類加載器。
一般來(lái)說(shuō),Java 程序員并不需要直接同類加載器進(jìn)行交互。JVM 默認(rèn)的行為就已經(jīng)足夠滿足大多數(shù)情況的需求了。不過(guò),如果遇到了需要和類加載器進(jìn)行交互的情況,而對(duì)類加載器的機(jī)制又不是很了解的話,就不得不花大量的時(shí)間去調(diào)試
ClassNotFoundException 和 NoClassDefFoundError 等異常。
對(duì)于任意一個(gè)類,都需要由它的類加載器和這個(gè)類本身一同確定其在 JVM 中的唯一性。也就是說(shuō),如果兩個(gè)類的加載器不同,即使兩個(gè)類來(lái)源于同一個(gè)字節(jié)碼文件,那這兩個(gè)類就必定不相等(比如兩個(gè)類的 Class 對(duì)象不 equals)。
站在程序員的角度來(lái)看,Java 類加載器可以分為三種。
1)啟動(dòng)類加載器(Bootstrap Class-Loader),加載 jre/lib 包下面的 jar 文件,比如說(shuō)常見(jiàn)的 rt.jar。
2)擴(kuò)展類加載器(Extension or Ext Class-Loader),加載 jre/lib/ext 包下面的 jar 文件。
3)應(yīng)用類加載器(Application or App Clas-Loader),根據(jù)程序的類路徑(classpath)來(lái)加載 Java 類。
來(lái)來(lái)來(lái),通過(guò)一段簡(jiǎn)單的代碼了解下。
public class Test { public static void main(String[] args) { ClassLoader loader = Test.class.getClassLoader(); while (loader != null) { System.out.println(loader.toString()); loader = loader.getParent(); } } }
每個(gè) Java 類都維護(hù)著一個(gè)指向定義它的類加載器的引用,通過(guò) 類名.class.getClassLoader() 可以獲取到此引用;然后通過(guò) loader.getParent() 可以獲取類加載器的上層類加載器。
這段代碼的輸出結(jié)果如下:
sun.misc.Launcher$AppClassLoader@73d16e93 sun.misc.Launcher$ExtClassLoader@15db9742
第一行輸出為 Test 的類加載器,即應(yīng)用類加載器,它是 sun.misc.Launcher$AppClassLoader 類的實(shí)例;第二行輸出為擴(kuò)展類加載器,是 sun.misc.Launcher$ExtClassLoader 類的實(shí)例。那啟動(dòng)類加載器呢?
按理說(shuō),擴(kuò)展類加載器的上層類加載器是啟動(dòng)類加載器,但在我這個(gè)版本的 JDK 中, 擴(kuò)展類加載器的 getParent() 返回 null。所以沒(méi)有輸出。
04、雙親委派模型
如果以上三種類加載器不能滿足要求的話,程序員還可以自定義類加載器(繼承 java.lang.ClassLoader 類),它們之間的層級(jí)關(guān)系如下圖所示。
這種層次關(guān)系被稱作為雙親委派模型:如果一個(gè)類加載器收到了加載類的請(qǐng)求,它會(huì)先把請(qǐng)求委托給上層加載器去完成,上層加載器又會(huì)委托上上層加載器,一直到最頂層的類加載器;如果上層加載器無(wú)法完成類的加載工作時(shí),當(dāng)前類加載器才會(huì)嘗試自己去加載這個(gè)類。
PS:雙親委派模型突然讓我聯(lián)想到朱元璋同志,這個(gè)同志當(dāng)上了皇帝之后連宰相都不要了,所有的事情都親力親為,只有自己沒(méi)精力沒(méi)時(shí)間做的事才交給大臣們?nèi)ジ伞?/p>
使用雙親委派模型有一個(gè)很明顯的好處,那就是 Java 類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,這對(duì)于保證 Java 程序的穩(wěn)定運(yùn)作很重要。
上文中曾提到,如果兩個(gè)類的加載器不同,即使兩個(gè)類來(lái)源于同一個(gè)字節(jié)碼文件,那這兩個(gè)類就必定不相等——雙親委派模型能夠保證同一個(gè)類最終會(huì)被特定的類加載器加載。
看完了這篇文章,相信你對(duì)Java的類加載機(jī)制是什么有了一定的了解,想了解更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!