JVM 類加載機(jī)制及雙親委派模型是什么,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
為衛(wèi)東等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及衛(wèi)東網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、衛(wèi)東網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長期合作。這樣,我們也可以走得更遠(yuǎn)!
Java 程序是如何跑起來的呢,如何從一個(gè) .java 源文件到控制臺(tái)的輸出結(jié)果?
要回答類似的問題就需要學(xué)習(xí)虛擬機(jī)類加載機(jī)制。
整體的流程
Java 中的所有類,必須被裝載到 jvm 中才能運(yùn)行,這個(gè)裝載工作是由 jvm 中的類加載器完成的,類加載器所做的工作實(shí)質(zhì)是把類文件從硬盤讀取到內(nèi)存中,JVM 在加載類的時(shí)候,都是通過 ClassLoader 的 loadClass()方法來加載 class 的,loadClass 使用雙親委派模型。
以上流程中出現(xiàn)了很多陌生的名詞,本篇文章就是解析這些名詞,當(dāng)你回頭再看這句話便豁然開朗。
先解析一下這張圖,圖表示類的整個(gè)聲明周期,類從被加載到虛擬機(jī)內(nèi)存開始,到卸載出內(nèi)存為止,包含 7 個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析 3 個(gè)階段統(tǒng)稱為連接。
加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這 5 個(gè)階段的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始,這是為了支持 Java 語言的運(yùn)行時(shí)綁定(動(dòng)態(tài)綁定或晚期綁定)。
裝載兩個(gè)字說起來簡(jiǎn)單,但是對(duì)于 JVM 來說,這是個(gè)復(fù)雜的流程,也就是虛擬機(jī)的類加載機(jī)制:虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的 Java 類型。
加載
這里所說的「加載」是「類加載」過程的一個(gè)階段,「類加載」描述的是整個(gè)過程,「加載」僅表示「類加載」的第一階段,需要完成以下三件事情:
通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個(gè)代表該類的 java.lang.Class 對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口
說這么多其實(shí)就完成了一件事情:根據(jù)一個(gè)類的名字(全限定名)在內(nèi)存中生成一個(gè) Class 對(duì)象,注意 Class 對(duì)象不是關(guān)鍵字 new 出來的那個(gè)對(duì)象,Class 是一種類型,表示的是一個(gè)對(duì)象的運(yùn)行時(shí)類型信息。
接下來的三個(gè)階段,都屬于連接(Linking)。加載階段的部分內(nèi)容(如一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開始。
連接 - 驗(yàn)證
驗(yàn)證是為了確保 Class 文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。如果驗(yàn)證到輸入的字節(jié)流不符合 Class 文件格式的約束,虛擬機(jī)就會(huì)拋出一個(gè) java.lang.VerifyError 異常或其子類異常。
驗(yàn)證階段大致完成 4 個(gè)階段的檢驗(yàn)動(dòng)作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號(hào)引用驗(yàn)證。
連接 - 準(zhǔn)備
準(zhǔn)備階段是正式為類變量(static 修飾的變量)分配內(nèi)存并設(shè)置類變量初始值的極端,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。注意此時(shí)進(jìn)行內(nèi)存分配的僅包括類變量,而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在 Java 堆中。
并且這里提到的初始值是指零值,每種基本數(shù)據(jù)類型都有對(duì)應(yīng)的零值。
假設(shè)一個(gè)類變量的定義為: public static int value = 123
那這個(gè)變量在準(zhǔn)備階段過后的初始值是 0 而不是 123,把 value 賦值為 123 的動(dòng)作將在初始化階段才會(huì)執(zhí)行
連接 - 解析
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程
符號(hào)引用:只包含語義信息,不涉及具體實(shí)現(xiàn),以一組符號(hào)來描述引用目標(biāo),是字面量;符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。
直接引用:與具體實(shí)現(xiàn)息息相關(guān),是直接指向目標(biāo)的指針;直接引用是可以直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
初始化
初始化階段,才真正開始執(zhí)行類中定義的 Java 程序代碼(或者說是字節(jié)碼)
在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,而在初始化階段,則根據(jù)程序員通過程序制定的主觀計(jì)劃去初始化類變量和其他資源。
也就是我們通常理解的賦初始值以及執(zhí)行靜態(tài)代碼塊。
類與類加載器
對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在 Java 虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。
比較兩個(gè)類是否「相等」,只有在這兩個(gè)類是由同一個(gè)類加載器加載的前提下才有意義,否則,即使這兩個(gè)類來源于同一個(gè) Class 文件,被同一個(gè)虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等
加載器的種類(從開發(fā)人員的角度)
啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)將存放在
擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載
應(yīng)用程序類加載器(Application ClassLoader):也稱為系統(tǒng)類加載器,負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫。如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器
上圖所示的類加載器之間的層次關(guān)系,稱為類加載器的雙親委派模型。
雙親委派模型除了要求頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不會(huì)以繼承的關(guān)系類實(shí)現(xiàn),而是都使用組合關(guān)系來復(fù)用父加載器的代碼。
雙親委派模型的工作過程:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。
為什么要使用雙親委派模型
借用一個(gè)例子:黑客自定義一個(gè) java.lang.String 類,該 String 類具有系統(tǒng)的String 類一樣的功能,只是在某個(gè)函數(shù)稍作修改。比如 equals 函數(shù),這個(gè)函數(shù)經(jīng)常使用,如果在這這個(gè)函數(shù)中,黑客加入一些“病毒代碼”。并且通過自定義類加載器加入到 JVM 中。此時(shí),如果沒有雙親委派模型,那么JVM就可能誤以為黑客自定義的 java.lang.String 類是系統(tǒng)的 String 類,導(dǎo)致“病毒代碼”被執(zhí)行。
而有了雙親委派模型,黑客自定義的 java.lang.String 類永遠(yuǎn)都不會(huì)被加載進(jìn)內(nèi)存。因?yàn)槭紫仁亲铐敹说念惣虞d器加載系統(tǒng)的 java.lang.String 類,最終自定義的類加載器無法加載 java.lang.String 類。
也就是說,無論那一個(gè)類加載器去加載一個(gè)系統(tǒng)中已有的類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此系統(tǒng)里在程序的各種類加載器環(huán)境中都是同一個(gè)類。
雙親委派模型是如何實(shí)現(xiàn)的
實(shí)現(xiàn)雙親委派的代碼都幾種在 java.lang.ClassLoader 的 loadClass() 方法中:先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的 loadClass() 方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父加載器加載失敗,拋出 ClassNotFoundException 異常后,再調(diào)用自己的 findClass() 方法進(jìn)行加載。(看源碼后發(fā)現(xiàn)這里的拋出異常是被吞了,catch 之后不會(huì)做任何操作)
雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是 Java 設(shè)計(jì)者推薦給開發(fā)者的類加載器的實(shí)現(xiàn)方式。大部分的類加載器都遵循這個(gè)模型,但雙親委派模型也可以被破壞,破壞并不是不好,而是在有足夠意義和理由的情況下,突破已有的規(guī)則進(jìn)行創(chuàng)建,實(shí)現(xiàn)特定的功能。
三種破壞雙親委派模型的方式
重寫 loadClass() 方法
逆向使用類加載器,引入線程上下文類加載器
追求程序的動(dòng)態(tài)性:代碼熱替換、模塊熱部署等技術(shù)
看完上述內(nèi)容,你們掌握J(rèn)VM 類加載機(jī)制及雙親委派模型是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
本文題目:JVM類加載機(jī)制及雙親委派模型是什么
文章URL:http://aaarwkj.com/article24/gjoice.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站建設(shè)、域名注冊(cè)、網(wǎng)站收錄、標(biāo)簽優(yōu)化、軟件開發(fā)、網(wǎng)站設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)