溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java的類加載機制是什么

發(fā)布時間:2020-11-09 14:16:14 來源:億速云 閱讀:122 作者:小新 欄目:編程語言

小編給大家分享一下Java的類加載機制是什么,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!

01、字節(jié)碼

在聊 Java 類加載機制之前,需要先了解一下 Java 字節(jié)碼,因為它和類加載機制息息相關(guān)。

計算機只認(rèn)識 0 和 1,所以任何語言編寫的程序都需要編譯成機器碼才能被計算機理解,然后執(zhí)行,Java 也不例外。

Java 在誕生的時候喊出了一個非常牛逼的口號:“Write Once, Run Anywhere”,為了達(dá)成這個目的,Sun 公司發(fā)布了許多可以在不同平臺(Windows、Linux)上運行的 Java 虛擬機(JVM)——負(fù)責(zé)載入和執(zhí)行 Java 編譯后的字節(jié)碼。

Java的類加載機制是什么

到底 Java 字節(jié)碼是什么樣子,我們借助一段簡單的代碼來看一看。

源碼如下:

package com.cmower.java_demo;

public class Test {

    public static void main(String[] args) {
        System.out.println("版權(quán)聲明");
    }

}

代碼編譯通過后,通過 xxd Test.class 命令查看一下這個字節(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  ..<init>...()V..
00000050: 0443 6f64 650a 0003 0009 0c00 0500 0601  .Code...........
00000060: 000f 4c69 6e65 4e75 6d62 6572 5461 626c  ..LineNumberTabl

感覺有點懵逼,對不對?

懵就對了。

這段字節(jié)碼中的 cafe babe 被稱為“魔數(shù)”,是 JVM 識別 .class 文件的標(biāo)志。文件格式的定制者可以自由選擇魔數(shù)值(只要沒用過),比如說 .png 文件的魔數(shù)是 8950 4e47。

至于其他內(nèi)容嘛,可以選擇忘記了。

02、類加載過程

了解了 Java 字節(jié)碼后,我們來聊聊 Java 的類加載過程。

Java 的類加載過程可以分為 5 個階段:載入、驗證、準(zhǔn)備、解析和初始化。這 5 個階段一般是順序發(fā)生的,但在動態(tài)綁定的情況下,解析階段發(fā)生在初始化階段之后。

1)Loading(載入)

JVM 在該階段的主要目的是將字節(jié)碼從不同的數(shù)據(jù)源(可能是 class 文件、也可能是 jar 包,甚至網(wǎng)絡(luò))轉(zhuǎn)化為二進(jìn)制字節(jié)流加載到內(nèi)存中,并生成一個代表該類的 java.lang.Class 對象。

2)Verification(驗證)

JVM 會在該階段對二進(jìn)制字節(jié)流進(jìn)行校驗,只有符合 JVM 字節(jié)碼規(guī)范的才能被 JVM 正確執(zhí)行。該階段是保證 JVM 安全的重要屏障,下面是一些主要的檢查。

確保二進(jìn)制字節(jié)流格式符合預(yù)期(比如說是否以 cafe bene 開頭)。

是否所有方法都遵守訪問控制關(guān)鍵字的限定。

方法調(diào)用的參數(shù)個數(shù)和類型是否正確。

確保變量在使用之前被正確初始化了。

檢查變量是否被賦予恰當(dāng)類型的值。

3)Preparation(準(zhǔn)備)

JVM 會在該階段對類變量(也稱為靜態(tài)變量,static 關(guān)鍵字修飾的)分配內(nèi)存并初始化(對應(yīng)數(shù)據(jù)類型的默認(rèn)初始值,如 0、0L、null、false 等)。

也就是說,假如有這樣一段代碼:

public String chenmo = "沉默";
public static String wanger = "王二";
public static final String cmower = "沉默王二";

chenmo 不會被分配內(nèi)存,而 wanger 會;但 wanger 的初始值不是“王二”而是 null。

需要注意的是,static final 修飾的變量被稱作為常量,和類變量不同。常量一旦賦值就不會改變了,所以 cmower 在準(zhǔn)備階段的值為“沉默王二”而不是 null。

4)Resolution(解析)

該階段將常量池中的符號引用轉(zhuǎn)化為直接引用。

what?符號引用,直接引用?

符號引用以一組符號(任何形式的字面量,只要在使用時能夠無歧義的定位到目標(biāo)即可)來描述所引用的目標(biāo)。

在編譯時,Java 類并不知道所引用的類的實際地址,因此只能使用符號引用來代替。比如 com.Wanger 類引用了 com.Chenmo 類,編譯時 Wanger 類并不知道 Chenmo 類的實際內(nèi)存地址,因此只能使用符號 com.Chenmo。

直接引用通過對符號引用進(jìn)行解析,找到引用的實際內(nèi)存地址。

5)Initialization(初始化)

該階段是類加載過程的最后一步。在準(zhǔn)備階段,類變量已經(jīng)被賦過默認(rèn)初始值,而在初始化階段,類變量將被賦值為代碼期望賦的值。換句話說,初始化階段是執(zhí)行類構(gòu)造器方法的過程。

oh,no,上面這段話說得很抽象,不好理解,對不對,我來舉個例子。

String cmower = new String("沉默王二");

上面這段代碼使用了 new 關(guān)鍵字來實例化一個字符串對象,那么這時候,就會調(diào)用 String 類的構(gòu)造方法對 cmower 進(jìn)行實例化。

03、類加載器

聊完類加載過程,就不得不聊聊類加載器。

一般來說,Java 程序員并不需要直接同類加載器進(jìn)行交互。JVM 默認(rèn)的行為就已經(jīng)足夠滿足大多數(shù)情況的需求了。不過,如果遇到了需要和類加載器進(jìn)行交互的情況,而對類加載器的機制又不是很了解的話,就不得不花大量的時間去調(diào)試

ClassNotFoundException 和 NoClassDefFoundError 等異常。

對于任意一個類,都需要由它的類加載器和這個類本身一同確定其在 JVM 中的唯一性。也就是說,如果兩個類的加載器不同,即使兩個類來源于同一個字節(jié)碼文件,那這兩個類就必定不相等(比如兩個類的 Class 對象不 equals)。

站在程序員的角度來看,Java 類加載器可以分為三種。

1)啟動類加載器(Bootstrap Class-Loader),加載 jre/lib 包下面的 jar 文件,比如說常見的 rt.jar。

2)擴(kuò)展類加載器(Extension or Ext Class-Loader),加載 jre/lib/ext 包下面的 jar 文件。

3)應(yīng)用類加載器(Application or App Clas-Loader),根據(jù)程序的類路徑(classpath)來加載 Java 類。

來來來,通過一段簡單的代碼了解下。

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

}

每個 Java 類都維護(hù)著一個指向定義它的類加載器的引用,通過 類名.class.getClassLoader() 可以獲取到此引用;然后通過 loader.getParent() 可以獲取類加載器的上層類加載器。

這段代碼的輸出結(jié)果如下:

sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742

第一行輸出為 Test 的類加載器,即應(yīng)用類加載器,它是 sun.misc.Launcher$AppClassLoader 類的實例;第二行輸出為擴(kuò)展類加載器,是 sun.misc.Launcher$ExtClassLoader 類的實例。那啟動類加載器呢?

按理說,擴(kuò)展類加載器的上層類加載器是啟動類加載器,但在我這個版本的 JDK 中, 擴(kuò)展類加載器的 getParent() 返回 null。所以沒有輸出。

04、雙親委派模型

如果以上三種類加載器不能滿足要求的話,程序員還可以自定義類加載器(繼承 java.lang.ClassLoader 類),它們之間的層級關(guān)系如下圖所示。

Java的類加載機制是什么

這種層次關(guān)系被稱作為雙親委派模型:如果一個類加載器收到了加載類的請求,它會先把請求委托給上層加載器去完成,上層加載器又會委托上上層加載器,一直到最頂層的類加載器;如果上層加載器無法完成類的加載工作時,當(dāng)前類加載器才會嘗試自己去加載這個類。

PS:雙親委派模型突然讓我聯(lián)想到朱元璋同志,這個同志當(dāng)上了皇帝之后連宰相都不要了,所有的事情都親力親為,只有自己沒精力沒時間做的事才交給大臣們?nèi)ジ伞?/p>

使用雙親委派模型有一個很明顯的好處,那就是 Java 類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系,這對于保證 Java 程序的穩(wěn)定運作很重要。

上文中曾提到,如果兩個類的加載器不同,即使兩個類來源于同一個字節(jié)碼文件,那這兩個類就必定不相等——雙親委派模型能夠保證同一個類最終會被特定的類加載器加載。

看完了這篇文章,相信你對Java的類加載機制是什么有了一定的了解,想了解更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI