溫馨提示×

溫馨提示×

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

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

JVM 類加載機(jī)制及雙親委派模型是什么

發(fā)布時間:2021-10-23 16:15:46 來源:億速云 閱讀:171 作者:柒染 欄目:大數(shù)據(jù)

JVM 類加載機(jī)制及雙親委派模型是什么,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

Java 程序是如何跑起來的呢,如何從一個 .java 源文件到控制臺的輸出結(jié)果? 

要回答類似的問題就需要學(xué)習(xí)虛擬機(jī)類加載機(jī)制。

整體的流程

Java 中的所有類,必須被裝載到 jvm 中才能運(yùn)行,這個裝載工作是由 jvm 中的類加載器完成的,類加載器所做的工作實(shí)質(zhì)是把類文件從硬盤讀取到內(nèi)存中,JVM 在加載類的時候,都是通過 ClassLoader 的 loadClass()方法來加載 class 的,loadClass 使用雙親委派模型。

以上流程中出現(xiàn)了很多陌生的名詞,本篇文章就是解析這些名詞,當(dāng)你回頭再看這句話便豁然開朗。

JVM 類加載機(jī)制及雙親委派模型是什么  

類的聲明周期

先解析一下這張圖,圖表示類的整個聲明周期,類從被加載到虛擬機(jī)內(nèi)存開始,到卸載出內(nèi)存為止,包含 7 個階段,其中驗(yàn)證、準(zhǔn)備、解析 3 個階段統(tǒng)稱為連接。

加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這 5 個階段的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始,這是為了支持 Java 語言的運(yùn)行時綁定(動態(tài)綁定或晚期綁定)。

裝載

裝載兩個字說起來簡單,但是對于 JVM 來說,這是個復(fù)雜的流程,也就是虛擬機(jī)的類加載機(jī)制:虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的 Java 類型。

加載

這里所說的「加載」是「類加載」過程的一個階段,「類加載」描述的是整個過程,「加載」僅表示「類加載」的第一階段,需要完成以下三件事情:

  • 通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流

  • 將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)

  • 在內(nèi)存中生成一個代表該類的 java.lang.Class 對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口

說這么多其實(shí)就完成了一件事情:根據(jù)一個類的名字(全限定名)在內(nèi)存中生成一個 Class 對象,注意 Class 對象不是關(guān)鍵字 new 出來的那個對象,Class 是一種類型,表示的是一個對象的運(yùn)行時類型信息。

接下來的三個階段,都屬于連接(Linking)。加載階段的部分內(nèi)容(如一部分字節(jié)碼文件格式驗(yàn)證動作)是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始。

連接 - 驗(yàn)證

驗(yàn)證是為了確保 Class 文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全。如果驗(yàn)證到輸入的字節(jié)流不符合 Class 文件格式的約束,虛擬機(jī)就會拋出一個 java.lang.VerifyError 異?;蚱渥宇惍惓!?/p>

驗(yàn)證階段大致完成 4 個階段的檢驗(yàn)動作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號引用驗(yàn)證。

連接 - 準(zhǔn)備

準(zhǔn)備階段是正式為類變量(static 修飾的變量)分配內(nèi)存并設(shè)置類變量初始值的極端,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。注意此時進(jìn)行內(nèi)存分配的僅包括類變量,而不包括實(shí)例變量,實(shí)例變量將會在對象實(shí)例化時隨著對象一起分配在 Java 堆中。

并且這里提到的初始值是指零值,每種基本數(shù)據(jù)類型都有對應(yīng)的零值。

假設(shè)一個類變量的定義為: 
public static int value = 123

那這個變量在準(zhǔn)備階段過后的初始值是 0 而不是 123,把 value 賦值為 123 的動作將在初始化階段才會執(zhí)行

連接 - 解析

解析階段是虛擬機(jī)將常量池內(nèi)的符號引用替換為直接引用的過程

符號引用:只包含語義信息,不涉及具體實(shí)現(xiàn),以一組符號來描述引用目標(biāo),是字面量;符號引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。

直接引用:與具體實(shí)現(xiàn)息息相關(guān),是直接指向目標(biāo)的指針;直接引用是可以直接指向目標(biāo)的指針、相對偏移量或是一個能間接定位到目標(biāo)的句柄。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

初始化

初始化階段,才真正開始執(zhí)行類中定義的 Java 程序代碼(或者說是字節(jié)碼)

在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,而在初始化階段,則根據(jù)程序員通過程序制定的主觀計(jì)劃去初始化類變量和其他資源。

也就是我們通常理解的賦初始值以及執(zhí)行靜態(tài)代碼塊。

類加載器

類與類加載器

對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在 Java 虛擬機(jī)中的唯一性,每一個類加載器,都擁有一個獨(dú)立的類名稱空間。

比較兩個類是否「相等」,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個 Class 文件,被同一個虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個類就必定不相等

加載器的種類(從開發(fā)人員的角度)

  • 啟動類加載器(Bootstrap ClassLoader):負(fù)責(zé)將存放在 

    \lib 目錄中的,或者被 -Xbootclasspath 參數(shù)所指定的路徑中的,并且是虛擬機(jī)識別的(僅按文件名識別,如 rt.jar,名字不符合的類庫即使放在 lib 目錄中也不會被加載)類庫加載到虛擬機(jī)內(nèi)存中

  • 擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載 

    \lib\ext 目錄中的,或者被 java.ext.dirs 系統(tǒng)變量所指定的路徑中的所有類庫

  • 應(yīng)用程序類加載器(Application ClassLoader):也稱為系統(tǒng)類加載器,負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫。如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認(rèn)的類加載器

雙親委派模型

JVM 類加載機(jī)制及雙親委派模型是什么  

類加載器雙親委派模型

上圖所示的類加載器之間的層次關(guān)系,稱為類加載器的雙親委派模型。

雙親委派模型除了要求頂層的啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不會以繼承的關(guān)系類實(shí)現(xiàn),而是都使用組合關(guān)系來復(fù)用父加載器的代碼。

雙親委派模型的工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。

為什么要使用雙親委派模型

借用一個例子:黑客自定義一個 java.lang.String 類,該 String 類具有系統(tǒng)的String 類一樣的功能,只是在某個函數(shù)稍作修改。比如 equals 函數(shù),這個函數(shù)經(jīng)常使用,如果在這這個函數(shù)中,黑客加入一些“病毒代碼”。并且通過自定義類加載器加入到 JVM 中。此時,如果沒有雙親委派模型,那么JVM就可能誤以為黑客自定義的 java.lang.String 類是系統(tǒng)的 String 類,導(dǎo)致“病毒代碼”被執(zhí)行。

而有了雙親委派模型,黑客自定義的 java.lang.String 類永遠(yuǎn)都不會被加載進(jìn)內(nèi)存。因?yàn)槭紫仁亲铐敹说念惣虞d器加載系統(tǒng)的 java.lang.String 類,最終自定義的類加載器無法加載 java.lang.String 類。

也就是說,無論那一個類加載器去加載一個系統(tǒng)中已有的類,最終都是委派給處于模型最頂端的啟動類加載器進(jìn)行加載,因此系統(tǒng)里在程序的各種類加載器環(huán)境中都是同一個類。

雙親委派模型是如何實(shí)現(xiàn)的

實(shí)現(xiàn)雙親委派的代碼都幾種在 java.lang.ClassLoader 的 loadClass() 方法中:先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的 loadClass() 方法,若父加載器為空則默認(rèn)使用啟動類加載器作為父加載器。如果父加載器加載失敗,拋出 ClassNotFoundException 異常后,再調(diào)用自己的 findClass() 方法進(jìn)行加載。(看源碼后發(fā)現(xiàn)這里的拋出異常是被吞了,catch 之后不會做任何操作)

破壞雙親委派模型

雙親委派模型并不是一個強(qiáng)制性的約束模型,而是 Java 設(shè)計(jì)者推薦給開發(fā)者的類加載器的實(shí)現(xiàn)方式。大部分的類加載器都遵循這個模型,但雙親委派模型也可以被破壞,破壞并不是不好,而是在有足夠意義和理由的情況下,突破已有的規(guī)則進(jìn)行創(chuàng)建,實(shí)現(xiàn)特定的功能。

三種破壞雙親委派模型的方式

  • 重寫 loadClass() 方法

  • 逆向使用類加載器,引入線程上下文類加載器

  • 追求程序的動態(tài)性:代碼熱替換、模塊熱部署等技術(shù)

看完上述內(nèi)容,你們掌握J(rèn)VM 類加載機(jī)制及雙親委派模型是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

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

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

jvm
AI