jvm类加载机制_JVM 类加载机制
學(xué)習(xí)導(dǎo)圖
一.為什么要學(xué)習(xí)類加載機(jī)制?
今天想跟大家嘮嗑嘮嗑 Java 的類加載機(jī)制,這是 Java 的一個(gè)很重要的創(chuàng)新點(diǎn),曾經(jīng)也是 Java 流行的重要原因之一。
Oracle 當(dāng)初引入這個(gè)機(jī)制是為了滿足 Java Applet 開發(fā)的需求, JVM 咬咬牙引入了 Java 類加載機(jī)制,后來(lái)的基于 Jvm 的動(dòng)態(tài)部署,插件化開發(fā)包括大家熱議的熱修復(fù),總之很多后來(lái)的技術(shù)都源于在 JVM 中引入了類加載器。
如今,類加載機(jī)制也在各個(gè)領(lǐng)域大放異彩,在面試中,由類加載機(jī)制所衍生出來(lái)各類面試題也層出不窮。
所以,我們要了解下類加載機(jī)制,為工作中或者是面試中實(shí)際的需要打好良好的基礎(chǔ)。
二.核心知識(shí)點(diǎn)歸納
2.1 概述
Q1:JVM類加載機(jī)制定義:
虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件 加載 到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行 校驗(yàn) 、 轉(zhuǎn)換解析 和 初始化 ,最終形成可被虛擬機(jī)直接使用的 Java 類型的過(guò)程
Q2:特性
運(yùn)行期類加載。即在 Java 語(yǔ)言里面,類型的加載、連接和初始化過(guò)程都是在程序 運(yùn)行期 完成的,從而通過(guò)犧牲一些性能開銷來(lái)?yè)Q取 Java 程序的高度靈活性
什么是運(yùn)行期,什么是編譯期?
- 編譯期 是指編譯器將 源代碼翻譯 為 機(jī)器能識(shí)別的代碼 , Java 被編譯為 Jvm 認(rèn)識(shí)的 字節(jié)碼文件
- 運(yùn)行期 則是指 Java 代碼的 運(yùn)行 過(guò)程
JVM 運(yùn)行期動(dòng)態(tài)加載+動(dòng)態(tài)連接-> Java 的動(dòng)態(tài)擴(kuò)展特性
2.2 類加載的過(guò)程
類從被加載到虛擬機(jī)內(nèi)存中開始、到卸載出內(nèi)存為止,整個(gè)生命周期包括七個(gè)階段:
- 加載
- 驗(yàn)證
- 準(zhǔn)備
- 解析
- 初始化
- 使用
- 卸載
其中,驗(yàn)證、準(zhǔn)備、解析這3個(gè)部分統(tǒng)稱為 連接 ,流程如下圖:
注意:
『加載』->『驗(yàn)證』->『準(zhǔn)備』->『初始化』->『卸載』這五個(gè)階段的順序是確定的,而『解析』可能為了支持Java的動(dòng)態(tài)綁定會(huì)在『初始化』后才開始上述階段通常都是互相交叉地混合式進(jìn)行的,比如會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用、激活另外一個(gè)階段2.2.1 加載
Q1:任務(wù)
- 通過(guò)類的全限定名來(lái)獲取定義此類的 二進(jìn)制字節(jié)流 。如從 ZIP 包讀取、從網(wǎng)絡(luò)中獲取、通過(guò)運(yùn)行時(shí)計(jì)算生成、由其他文件生成、從數(shù)據(jù)庫(kù)中讀取等等途徑......
- 將該二進(jìn)制字節(jié)流所代表的 靜態(tài)存儲(chǔ)結(jié)構(gòu) 轉(zhuǎn)化為 方法區(qū) 的 運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu) ,該數(shù)據(jù)存儲(chǔ)數(shù)據(jù)結(jié)構(gòu)由虛擬機(jī)實(shí)現(xiàn)自行定義
- 在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class 對(duì)象,它將作為程序訪問(wèn)方法區(qū)中的這些類型數(shù)據(jù)的外部接口
2.2.2 驗(yàn)證
- 是 連接 階段的 第一步 ,且工作量在 JVM 類加載子系統(tǒng)中占了相當(dāng)大的一部分
- 目的:為了 確保 Class 文件的字節(jié)流中包含的信息 符合 當(dāng)前 虛擬機(jī)的要求 ,并且 不會(huì)危害虛擬機(jī)自身的安全
由此可見,它能直接決定 JVM 能否承受惡意代碼的攻擊,因此驗(yàn)證階段 很重要 ,但由于它對(duì)程序運(yùn)行期沒有影響,并 不一定必要 ,可以考慮使用 -Xverify:none 參數(shù)來(lái)關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載的時(shí)間。
- 檢驗(yàn)過(guò)程包括下面四個(gè)階段:A.文件格式驗(yàn)證:內(nèi)容:驗(yàn)證 字節(jié)流是否符合 Class 文件格式的規(guī)范 、以及是否能被 當(dāng)前版本的虛擬機(jī)處理目的:保證輸入的 字節(jié)流 能正確地解析并存儲(chǔ)于 方法區(qū) 之內(nèi),且格式上符合描述一個(gè) Java 類型信息的要求。只有保證二進(jìn)制字節(jié)流通過(guò)了該驗(yàn)證后,它才會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ),所以 后續(xù)3個(gè)驗(yàn)證階段全部是基于方法區(qū) 而不是字節(jié)流了例子:是否以魔數(shù) 0xCAFEBABE 開頭主次版本號(hào)是否在 JVM 接受范圍內(nèi)索引值是否有指向不存在/不符合類型的常量......B.元數(shù)據(jù)驗(yàn)證:內(nèi)容:對(duì)字節(jié)碼描述的信息進(jìn)行 語(yǔ)義 分析,以保證其描述的信息符合 Java 語(yǔ)言規(guī)范的要求目的:對(duì)類的 元數(shù)據(jù)信息 進(jìn)行語(yǔ)義校驗(yàn),保證不存在不符合 Java 語(yǔ)言規(guī)范的元數(shù)據(jù)信息例子:類是否有父類(除了 java.lang.Object 之外,所有類都應(yīng)有父類)父類是否繼承了不允許被繼承的類( final 修飾的類)如果該類不是抽象類,是否實(shí)現(xiàn)了其父類或接口中要求實(shí)現(xiàn)的所有方法......? C.字節(jié)碼驗(yàn)證:是驗(yàn)證過(guò)程中 最復(fù)雜 的一個(gè)階段內(nèi)容:對(duì)類的 方法體 進(jìn)行校驗(yàn)分析,保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的事件目的:通過(guò)數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的、符合邏輯的例子:保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作,例如不會(huì)出現(xiàn)“在操作數(shù)棧的數(shù)據(jù)類型中放置了 int 類型的數(shù)據(jù),使用時(shí)卻按 long 類型來(lái)載入本地變量表中”保證任何跳轉(zhuǎn)指令都不會(huì)跳轉(zhuǎn)到方法體外的字節(jié)碼指令上......? D.符號(hào)引用驗(yàn)證:內(nèi)容:對(duì) 類自身以外(如常量池中的各種符號(hào)引用)的信息 進(jìn)行匹配性校驗(yàn)?zāi)康?#xff1a;確保解析 動(dòng)作能正常執(zhí)行 ,如果無(wú)法通過(guò)符號(hào)引用驗(yàn)證,那么將會(huì)拋出一個(gè) java.lang.IncompatibleClassChangeError 異常的子類注意:該驗(yàn)證發(fā)生在虛擬機(jī)將 符號(hào)引用 轉(zhuǎn)化為 直接引用 的時(shí)候,即『 解析 』階段
2.2.3 準(zhǔn)備
Q1:任務(wù)
- 為類變量(靜態(tài)變量) 分配內(nèi)存 : 因?yàn)檫@里的變量是由方法區(qū)分配內(nèi)存 的,所以 僅包括類變量 而不包括實(shí)例變量,后者將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在 Java 堆中
- 設(shè)置類變量 初始值 :通常情況下零值
2.2.4 解析
之前提過(guò),解析階段就是虛擬機(jī)將 常量池 內(nèi)的 符號(hào)引用替換為直接引用 的過(guò)程
- 符號(hào)引用:以一組符號(hào)來(lái)描述所引用的目標(biāo)
- 可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可
- 與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),因?yàn)榉?hào)引用的字面量形式明確定義在 Java 虛擬機(jī)規(guī)范的 Class 文件格式中,所以即使各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局不同,但是能接受符號(hào)引用都是一致的
- 直接引用:
- 可以是直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄
- 與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān),同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不同
- 發(fā)生時(shí)間: JVM 會(huì)根據(jù)需要來(lái)判斷,是在類被加載器 加載時(shí) 就對(duì)常量池中的符號(hào)引用進(jìn)行解析,還是等到一個(gè)符號(hào)引用將要被 使用前 才去解析
- 解析動(dòng)作:有七類符號(hào)及其對(duì)應(yīng)在常量池的七種常量類型
- 類或接口 ( CONSTANT_Class_info )
- 字段 ( CONSTANT_Fieldref_info )
- 類方法 ( CONSTANT_Methodref_info )
- 接口方法 ( CONSTANT_InterfaceMethodref_info )
- 方法類型 ( CONSTANT_MethodType_info )
- 方法句柄 ( CONSTANT_MethodHandle_info )
- 調(diào)用點(diǎn)限定符 ( CONSTANT_InvokeDynamic_info )
舉個(gè)例子,設(shè)當(dāng)前代碼所處的為類 D ,把一個(gè)從未解析過(guò)的 符號(hào)引用 N 解析為一個(gè) 類或接口 C 的直接引用 ,解析過(guò)程分三步:
- 若 C 不是數(shù)組類型: JVM 將會(huì)把代表 N 的全限定名傳遞給 D 類加載器去加載這個(gè)類 C 。在加載過(guò)程中,由于 元數(shù)據(jù)驗(yàn)證 、 字節(jié)碼驗(yàn)證 的需要,又可能觸發(fā)其他相關(guān)類的加載動(dòng)作。一旦這個(gè)加載過(guò)程出現(xiàn)了任何異常,解析過(guò)程就宣告失敗。
- 若 C 是數(shù)組類型且數(shù)組元素類型為對(duì)象: JVM 也會(huì)按照上述規(guī)則加載數(shù)組元素類型
- 若上述步驟無(wú)任何異常:此時(shí) C 在 JVM 中已成為一個(gè)有效的類或接口,但在解析完成前還需進(jìn)行 符號(hào)引用驗(yàn)證 ,來(lái)確認(rèn) D 是否具備對(duì) C 的訪問(wèn)權(quán)限。如果發(fā)現(xiàn)不具備訪問(wèn)權(quán)限,將拋出 java.lang.IllegalAccessError 異常
Q1:字段(成員變量/域)和屬性有什么區(qū)別?
- 屬性,是指對(duì)象的屬性,對(duì)于 JavaBean 來(lái)說(shuō),是 getXXX 方法定義的
- 字段,是成員變量
2.2.5 初始化
- 是類加載過(guò)程的最后一步,會(huì)開始真正執(zhí)行類中定義的 Java 代碼。而之前的類加載過(guò)程中,除了在『 加載 』階段用戶應(yīng)用程序可通過(guò) 自定義類加載器 參與之外, 其余階段均由虛擬機(jī)主導(dǎo)和控制
- 與『準(zhǔn)備』階段的 區(qū)分 :
clinit :由編譯器自動(dòng)收集類中的所有 類變量(靜態(tài)變量)的賦值動(dòng)作 和靜態(tài)語(yǔ)句塊 static{} 中的語(yǔ)句合并產(chǎn)生
- 是 線程安全 的,在多線程環(huán)境中被正確地加鎖、同步
- 對(duì)于類或接口來(lái)說(shuō)是 非 必需的,如果一個(gè)類中 沒有靜態(tài)語(yǔ)句塊 ,也沒有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成 clinit
- 接口與類不同的是 ,執(zhí)行接口的 clinit 不需要先執(zhí)行父接口 的 clinit ,只有當(dāng)父接口中定義的變量使用時(shí),父接口才會(huì)初始化。另外, 接口的實(shí)現(xiàn)類在初始化時(shí) 也一樣不會(huì)執(zhí)行接口的 clinit
- 在虛擬機(jī)規(guī)范中,規(guī)定了有且只有五種情況 必須立即 對(duì)類進(jìn)行『 初始化 』:
- 遇到 new 、 getstatic 、 putstatic 或 invokestatic 這4條字節(jié)碼指令時(shí)
- 使用 java.lang.reflect 包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候
- 當(dāng)初始化一個(gè)類的時(shí)候,若發(fā)現(xiàn)其父類還未進(jìn)行初始化,需先觸發(fā)其父類的初始化
- 在虛擬機(jī)啟動(dòng)時(shí),需指定一個(gè)要執(zhí)行的 主類 ,虛擬機(jī)會(huì)先初始化它
- 當(dāng)使用 JDK1.7 的動(dòng)態(tài)語(yǔ)言支持時(shí),若一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果為 REF_getStatic 、 REF_putStatic 、 REF_invokeStatic 的方法句柄,且這個(gè)方法句柄所對(duì)應(yīng)的類未進(jìn)行初始化,需先觸發(fā)其初始化。
2.3 類加載器&雙親委派模型
每個(gè)類加載器,都擁有一個(gè)獨(dú)立的命名空間,它不僅用于加載類,還和這個(gè)類本身一起作為在 JVM 中的唯一標(biāo)識(shí)。所以比較兩個(gè)類是否相等,只要看它們是否由同一個(gè) 類加載器 加載,即使它們來(lái)源于同一個(gè) Class 文件且被同一個(gè) JVM 加載,只要加載它們的 類加載器不同,這兩個(gè)類就必定不相等
2.3.1 類加載器
從 JVM 的角度,可將類加載器分為兩種:
- 啟動(dòng)類加載器
- 由 C++ 語(yǔ)言實(shí)現(xiàn),是虛擬機(jī)自身的一部分
- 負(fù)責(zé)加載存放在 <JAVA_HOME>lib 目錄中、或被 -Xbootclasspath 參數(shù)所指定路徑中的、且可被虛擬機(jī)識(shí)別的類庫(kù)
- 無(wú)法被 Java 程序直接引用,如果自定義類加載器想要把加載請(qǐng)求委派給引導(dǎo)類加載器的話,可直接用 null 代替
- 其他類加載器:由 Java 語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類 java.lang.ClassLoader ,可被 Java 程序直接引用。常見幾種:
- 擴(kuò)展類加載器A.由 sun.misc.Launcher$ExtClassLoader 實(shí)現(xiàn)B.負(fù)責(zé)加載 <JAVA_HOME>libext 目錄中的、或者被 java.ext.dirs 系統(tǒng)變量所指定的路徑中的所有類庫(kù)
- 應(yīng)用程序類加載器A.是 默認(rèn) 的類加載器,是 ClassLoader#getSystemClassLoader() 的返回值,故又稱為 系統(tǒng)類加載器B.由 sun.misc.Launcher$App-ClassLoader 實(shí)現(xiàn)C.負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù)
- 自定義類加載器:如果以上類加載起不能滿足需求,可自定義
需要注意的是:雖然 數(shù)組類 不通過(guò)類加載器創(chuàng)建而是由 JVM 直接創(chuàng)建的,但仍與類加載器有密切關(guān)系,因?yàn)?數(shù)組類的元素類型最終還要靠類加載器去創(chuàng)建
2.3.2 雙親委派模型
- 定義:表示類加載器之間的層次關(guān)系
- 前提 :除了頂層啟動(dòng)類加載器外, 其余類加載器都應(yīng)當(dāng)有自己的父類加載器 ,且它們之間關(guān)系一般不會(huì)以 繼承 關(guān)系來(lái)實(shí)現(xiàn),而是通過(guò) 組合 關(guān)系來(lái)復(fù)用父加載器的代碼
- 工作過(guò)程 :若一個(gè)類加載器收到了類加載的請(qǐng)求,它先會(huì)把這個(gè)請(qǐng)求 委派 給父類加載器,并向上傳遞,最終請(qǐng)求都傳送到頂層的啟動(dòng)類加載器中。只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載
- 注意 :不是一個(gè)強(qiáng)制性的約束模型,而是 Java 設(shè)計(jì)者推薦給開發(fā)者的一種類加載器實(shí)現(xiàn)方式
- 優(yōu)點(diǎn) :類會(huì)隨著它的類加載器一起具備帶有 優(yōu)先級(jí) 的層次關(guān)系,可保證 Java 程序的穩(wěn)定運(yùn)作;實(shí)現(xiàn)簡(jiǎn)單,所有實(shí)現(xiàn)代碼都集中在 java.lang.ClassLoader的loadClass() 中
比如,某些類加載器要加載 java.lang.Object 類,最終都會(huì)委派給最頂端的啟動(dòng)類加載器去加載,這樣 Object 類在程序的各種類加載器環(huán)境中都是同一個(gè)類。
相反,系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的 Object 類, Java 類型體系中最基礎(chǔ)的行為也就無(wú)法保證,應(yīng)用程序也將會(huì)變得一片混亂
總結(jié)
以上是生活随笔為你收集整理的jvm类加载机制_JVM 类加载机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 初中数学四十二个几何模型_模型 | 一文
- 下一篇: 所属文件不可访问_日志文件写入失败(pe