Java虛擬機(jī)類加載機(jī)制(七)

讀書筆記 深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)現(xiàn)(第二版)

概述

深入了解了Class文件存儲(chǔ)格式的具體細(xì)節(jié)后祟偷,虛擬機(jī)如何加載這些Class文件属铁?Class文件中的信息進(jìn)入虛擬機(jī)后會(huì)發(fā)生什么變化?這是作者第七章講解的內(nèi)容。

虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化恶座,最終形成可以被虛擬機(jī)直接使用的 Java 類型,這就是虛擬機(jī)的類加載機(jī)制沥阳。

類加載都是在程序運(yùn)行期間完成的跨琳,雖然會(huì)增加程序一點(diǎn)性能開銷,但能為 Java 應(yīng)用提供高度的靈活性桐罕。通過依賴運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)連接特點(diǎn)使 Java 具備動(dòng)態(tài)擴(kuò)展的語言特性脉让。例如:

  • 編寫面向接口的應(yīng)用程序,可以等到運(yùn)行時(shí)再指定其實(shí)際的實(shí)現(xiàn)類
  • 通過 Java 預(yù)定義的和自定義類加載器在運(yùn)行時(shí)從其他地方加載二進(jìn)制流作為程序代買的一部分(Applet功炮、JSP溅潜、OSGi 技術(shù))

類加載的時(shí)機(jī)

類的生命周期包括:加載(Loading)、驗(yàn)證(Verification)薪伏、準(zhǔn)備(Preparation)滚澜、解析(Resolution)、初始化(Initialization)嫁怀、使用(Using)和卸載(Unloading)共 7 個(gè)階段设捐。

其中驗(yàn)證、準(zhǔn)備塘淑、解析 3 個(gè)部分統(tǒng)稱為連接(Linking)萝招,這 7 個(gè)階段的發(fā)生順序如下圖所示:

圖7-1 類加載階段發(fā)生順序

什么時(shí)候開始類加載過程的第一個(gè)階段:加載?

Java 虛擬機(jī)規(guī)范并沒有強(qiáng)制規(guī)定加載(Loading)的時(shí)機(jī)朴爬。但嚴(yán)格規(guī)定有且只有在以下 5 種情況時(shí)如果類沒有初始化即寒,則需要先觸發(fā)其初始化(Initialization)

初始化之前,自然會(huì)進(jìn)行加載連接母赵。

  1. 遇到 new(實(shí)例化對(duì)象)逸爵、getstatic(讀取除常量外靜態(tài)字段)、putstatic(設(shè)置讀取除常量外靜態(tài)字段) 或 invokestatic(調(diào)用類的靜態(tài)方法) 這 4 條字節(jié)碼指令時(shí)凹嘲,所在的類需要初始化师倔。
  2. 使用 java.lang.reflect 包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)。
  3. 初始化一個(gè)類時(shí)周蹭,如果其父類沒有初始化趋艘,則需先初始化其父類(接口除外,只有使用到父接口的時(shí)候才會(huì)初始化)凶朗。
  4. 虛擬機(jī)啟動(dòng)時(shí)會(huì)先初始化用戶指定的執(zhí)行主類(包含 main 方法的類)瓷胧。
  5. 使用 JDK1.7 動(dòng)態(tài)語言支持時(shí),java.lang.invoke.MethodHandle 實(shí)例最后解析的結(jié)果為 REF_getStatic棚愤、REF_putStatic搓萧、REF_invokeStatic 的方法句柄時(shí),則這個(gè)方法句柄對(duì)應(yīng)的類需要初始化宛畦。

上述 5 種場(chǎng)景中的行為成為對(duì)一個(gè)類主動(dòng)引用瘸洛。除了主動(dòng)引用之外,所有引用類的方式都不會(huì)觸發(fā)類的初始化次和,稱為被動(dòng)引用反肋。

類加載的過程

接下來講解加載、驗(yàn)證踏施、準(zhǔn)備石蔗、解析和初始化這 5 個(gè)階段所執(zhí)行的具體動(dòng)作。

加載

加載類加載過程第一個(gè)階段读规。在加載階段虛擬機(jī)要做 3 件事:

  1. 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制流抓督。
    • 可以從壓縮包中讀取,如:JAR束亏、EAR铃在、WAR 格式。
    • 從網(wǎng)絡(luò)讀取碍遍,如:Applet 定铜。
    • 運(yùn)行時(shí)計(jì)算生成,如:動(dòng)態(tài)代理技術(shù)在 java.lang.reflect.Proxy 中怕敬,通過 ProxyGenerator.generateProxyClass為特定接口生成形式為「*$Proxy」的代理類的二進(jìn)制字節(jié)流揣炕。
    • 由其他文件生成,如:JSP 應(yīng)用通過 JSP 文件生成對(duì)應(yīng)的 Class 類东跪。
    • 從數(shù)據(jù)庫中讀取畸陡,如:中間件服務(wù)器 SAP Netweaver 可以選擇把程序安裝到數(shù)據(jù)庫中來完成程序代碼在集群間分發(fā)鹰溜。
      ……
  2. 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
  3. 在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class 對(duì)象丁恭,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口曹动。

獲取類的二進(jìn)制流是開發(fā)人員可控性最強(qiáng)的,既可以通過系統(tǒng)提供的啟動(dòng)類加載器完成牲览,也可以由用戶自定義類加載器來控制字節(jié)流的獲取方式(重寫類加載器的 loadClass 方法)墓陈。

數(shù)組類比較特殊,它不通過類加載器創(chuàng)建第献,而是由 Java 虛擬機(jī)直接創(chuàng)建贡必。但數(shù)組的元素類型(Element Type)最終是要靠類加載器創(chuàng)建。

加載階段完成后庸毫,虛擬機(jī)外部的二進(jìn)制字節(jié)流就會(huì)按照所需的格式存儲(chǔ)在方法區(qū)中仔拟,存儲(chǔ)格式由虛擬機(jī)自行定義。然后在內(nèi)存中實(shí)例化一個(gè) java.lang.Class 對(duì)象(并沒有在堆中飒赃,Class 對(duì)象雖然是對(duì)象理逊,但在 HotSpot虛擬機(jī)中是存放在方法區(qū)里),這個(gè)對(duì)象將作為程序訪問方法區(qū)中這些類型數(shù)據(jù)的外部接口盒揉。

加載階段與連接階段的驗(yàn)證中一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作是交叉進(jìn)行的,加載階段尚未完成兑徘,連接階段就可能已經(jīng)開始刚盈。這兩個(gè)階段總體的開始時(shí)間仍然保持固定的先后順序。

驗(yàn)證

驗(yàn)證階段的目的是保證 Class 文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求挂脑,并不會(huì)危害虛擬機(jī)的安全藕漱。

如加載階段所述,Class 文件并不一定是 Java 源碼編譯而來崭闲,甚至可以用 16 進(jìn)制編輯器直接編寫肋联。虛擬機(jī)如果不進(jìn)行字節(jié)流驗(yàn)證,可能因載入有害字節(jié)流而導(dǎo)致系統(tǒng)崩潰刁俭。

驗(yàn)證階段大致上會(huì)完成 4 個(gè)階段的檢驗(yàn)動(dòng)作:文件格式驗(yàn)證橄仍、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證牍戚、符號(hào)引用驗(yàn)證

文件格式驗(yàn)證

驗(yàn)證字節(jié)流是否符合 Class 文件格式規(guī)范侮繁,能被當(dāng)前版本的虛擬機(jī)處理∪缧ⅲ可能包括以下驗(yàn)證點(diǎn):

  • 是否魔數(shù)以 0xCAFEBABE 開頭宪哩。
  • 主、次版本號(hào)是否在當(dāng)前虛擬機(jī)的處理范圍內(nèi)第晰。
  • 常量池中是否有不支持的常量類型(檢查常量 tag 標(biāo)志)锁孟。
  • CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 編碼的數(shù)據(jù)彬祖。
  • Class 文件中各個(gè)部分及文件本身是否有被刪除或附加的其他信息
    ……

此階段的驗(yàn)證是基于二進(jìn)制字節(jié)流,只有通過了這個(gè)階段后品抽,字節(jié)流才會(huì)進(jìn)入方法區(qū)內(nèi)進(jìn)行存儲(chǔ)储笑。后面的 3 個(gè)階段驗(yàn)證全都是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)進(jìn)行的,不會(huì)再操作字節(jié)流桑包。

元數(shù)據(jù)驗(yàn)證

對(duì)字節(jié)碼描述信息進(jìn)行語義分析南蓬,確保其描述信息符合 Java 語言規(guī)范的要求⊙屏耍可能包括以下驗(yàn)證點(diǎn):

  • 是否有父類(除了 java.lang.Object 之外所有類都有父類)赘方。
  • 父類是否繼承了不允許被繼承的類(被 final 修飾的類)。
  • 如果不是抽象類弱左,是否實(shí)現(xiàn)了父類或接口中要求實(shí)現(xiàn)的方法窄陡。
  • 類中的字段、方法是否與父類產(chǎn)生矛盾(如覆蓋了父類的 final 字段拆火,或出現(xiàn)不符合規(guī)則的方法重載跳夭,例如方法參數(shù)一樣,但返回值不同等)
    ……

字節(jié)碼驗(yàn)證

驗(yàn)證階段最復(fù)雜的階段们镜,主要目的是通過數(shù)據(jù)流和控制流分析币叹,確定程序的語義是合法的、符合邏輯的模狭。在對(duì)元數(shù)據(jù)信息中的數(shù)據(jù)類型做完校驗(yàn)后對(duì)方法體進(jìn)行校驗(yàn)分析颈抚,保證在運(yùn)行時(shí)方法不會(huì)做出危害虛擬機(jī)安全的事件。

由于數(shù)據(jù)流驗(yàn)證的高復(fù)雜性嚼鹉,為避免過多的時(shí)間消耗贩汉,JDK 1.6 以后 Javac 編譯器和 Java虛擬機(jī)進(jìn)行了一項(xiàng)優(yōu)化,給方法體的 Code 屬性的屬性表增加了一項(xiàng)名為 StackMapTable 屬性锚赤。這個(gè)屬性描述了方法體中所有的基本塊(按控制流拆分的代碼塊)開始時(shí)本地變量表和操作數(shù)棧應(yīng)有的狀態(tài)匹舞。在字節(jié)碼驗(yàn)證階段就不需要根據(jù)程序推導(dǎo)狀態(tài)合法性,只要檢查 StackMapTable 屬性中的記錄是否合法即可线脚。

理論上 StackMapTable 屬性也存在被篡改的可能赐稽。有可能在惡意篡改 Code 屬性的同事生成相應(yīng)的 StackMapTable屬性來騙過虛擬機(jī)類型校驗(yàn)。

符號(hào)引用驗(yàn)證

驗(yàn)證目的是確保符號(hào)引用轉(zhuǎn)直接引用在解析階段能正常執(zhí)行酒贬。符號(hào)引用驗(yàn)證可以看做是對(duì)類自身以外(常量池中的各種符號(hào)引用)的信息進(jìn)行匹配性校驗(yàn)又憨。通常需要校驗(yàn)一下內(nèi)容:

  • 符號(hào)引用中通過字符串描述的全限定名是否能找到對(duì)應(yīng)的類。
  • 在指定類中是否存在符合方法的字段描述符以及簡(jiǎn)單名稱所描述的方法和字段锭吨。
  • 符號(hào)引用中農(nóng)的類蠢莺、字段、方法的訪問性是否可被當(dāng)前類訪問
    ……

如果無法通過符號(hào)引用驗(yàn)證零如,將會(huì)拋出 java.lang.IncompatibleClassChangeError 異常的子類躏将,如 java.lang.IllegalAccessError锄弱、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError 等祸憋。

雖然驗(yàn)證階段十分重要会宪,但如果能保證自己編寫的以及第三方包中代碼都已經(jīng)反復(fù)使用和驗(yàn)證過,可以使用-Xverify:none參數(shù)來關(guān)閉大部分的驗(yàn)證措施蚯窥,以縮短加載時(shí)間掸鹅。

準(zhǔn)備

準(zhǔn)備階段是正式為類變量(除常量外,被 static 修飾的變量)在方法區(qū)分配內(nèi)存并設(shè)置類變量的初始值(數(shù)字類型為 0拦赠,布爾類型為 false巍沙,引用類型為 null……)階段。實(shí)例變量在準(zhǔn)備階段是不會(huì)設(shè)值的荷鼠,而是在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在 Java
堆中句携。

例如在下面的代碼中,類變量 value 在準(zhǔn)備階段后值為 0 而不是 123允乐。因?yàn)榇藭r(shí)尚未執(zhí)行任何 Java 方法矮嫉,把 123 賦值給 value 的 putstatic 指令是被程序編譯后存放在類構(gòu)造器 <client>() 方法中的,這個(gè)方法只有在初始化階段才會(huì)執(zhí)行牍疏。

public static int value = 123;

如果這個(gè)變量是常量蠢笋,類字段的字段屬性表中存在 ConstantValue 屬性,那么準(zhǔn)備階段變量 value 就會(huì)被初始化為 ConstantValue 屬性所指定的值鳞陨。例如:

public final static int value = 123;

編譯時(shí) Javac 將會(huì)為 value 生成 ConstantValue 屬性挺尿,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù) ConstantValue 值把 value 賦值為 123。

解析

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

  • 符號(hào)引用(Symbolic Reference):以一組符號(hào)來描述引用的目標(biāo),符號(hào)的字面量形式明確的定義在 Java 虛擬機(jī)規(guī)范中的 Class 文件格式中熟史。

  • 直接引用(Direct Reference):直接引用可以是直接指向目標(biāo)的指針馁害、相對(duì)偏移量或者是一個(gè)間接能定位到目標(biāo)的句柄。如果有了直接引用蹂匹,那目標(biāo)一定存在于內(nèi)存中碘菜。

虛擬機(jī)規(guī)范沒有規(guī)定解析階段發(fā)生的具體時(shí)間,可以自行決定到底在類被加載器加載時(shí)就對(duì)常量池中的符號(hào)要引用進(jìn)行解析限寞,還是等到一個(gè)符號(hào)引用將要被使用前才去解析他忍啸。

解析動(dòng)作主要針對(duì)類或接口、字段履植、類方法计雌、接口方法、方法類型玫霎、方法句柄和調(diào)用點(diǎn)限定符 7 類符號(hào)引用進(jìn)行凿滤。后 3 種符號(hào)引用與 JDK 1.7 新增的動(dòng)態(tài)語言支持息息相關(guān)妈橄。

類或接口的解析

完成類或接口中的符號(hào)引用解析,在對(duì)符號(hào)引用的類加載過程中可能觸發(fā)相關(guān)類的加載動(dòng)作翁脆,所有類加載完成后如果沒有異常眷蚓,還需要進(jìn)行符號(hào)引用的訪問權(quán)限驗(yàn)證。

字段解析

解析字段符號(hào)引用首先會(huì)解析字段所屬的類或接口的符號(hào)引用反番,如果沒有異常沙热,虛擬機(jī)規(guī)范要求按如下步驟進(jìn)行搜索:

  1. 如果所屬類本身就包含簡(jiǎn)單名稱和字段描述符與該字段匹配的字段,結(jié)束并返回這個(gè)字段罢缸。
  2. 否則篙贸,按照繼承關(guān)系從下往上遞歸搜索各個(gè)父接口,如果包含了匹配字段祖能,結(jié)束返回歉秫。
  3. 否則,按繼承關(guān)系從下往上遞歸搜索父類养铸,如果包含了匹配字段雁芙,結(jié)束并返回。
  4. 否則钞螟,查找失敗拋出 java.lang.NoSuchFieldError 異常兔甘。

查找完成后會(huì)對(duì)字段進(jìn)行訪問權(quán)限驗(yàn)證,沒有權(quán)限時(shí)拋出 java.lang.IllegalAccessError 異常鳞滨。

類方法解析

類方法解析與字段解析第一步一樣洞焙,先解析類方法所在的類或接口的符號(hào)引用,如果沒有異常拯啦,虛擬機(jī)規(guī)范要求按如下步驟進(jìn)行搜索:

  1. 類方法與接口方法的常量類型定義不同澡匪,如果發(fā)現(xiàn)類方法表中 class_index 中索引的是接口方法,直接拋出 java.lang.IncompatibleClassChangeError 異常褒链。
  2. 如果在所在類中找到與目標(biāo)簡(jiǎn)單名稱和描述符相同的方法(以下簡(jiǎn)稱「匹配」)唁情,直接返回這個(gè)方法的直接引用,查找結(jié)束甫匹。
  3. 否則甸鸟,在類的父類中遞歸查找是否有匹配的方法,如果有返回方法的直接引用兵迅,查找結(jié)束抢韭。
  4. 否則,在類實(shí)現(xiàn)的接口列表中及他們的父接口中遞歸查找是否有匹配的方法恍箭,如果有刻恭,證明此類是個(gè)抽象類,查找結(jié)束扯夭,拋出 java.lang.AbastractMethodError 異常吠各。
  5. 否則臀突,宣布查找方法失敗,拋出 java.lang.NoSuchMethodError 異常贾漏。
    最后對(duì)類方法進(jìn)行訪問權(quán)限驗(yàn)證候学,沒有權(quán)限時(shí)拋出 java.lang.IllegalAccessError 異常。

接口方法解析

與類方法解析第一步一樣纵散,先解析類方法所屬類或接口的符號(hào)引用梳码。如果解析成功,虛擬機(jī)規(guī)范要求按如下步驟進(jìn)行搜索:

  1. 與類方法解析不同伍掀,如果在接口方法表中發(fā)現(xiàn) class_index 中索引的是類方法掰茶,那就直接拋出 java.lang.IncompatibleClassChangeError 異常。
  2. 否則蜜笤,在接口中查找是否存在匹配的方法濒蒋,如果存在直接返回接口方法的直接引用,查找結(jié)束把兔。
  3. 否則沪伙,在接口的父接口中遞歸查找,直到 java.lang.Object 類(查找范圍可能會(huì)包括 Object 類)為止县好,搜索是否有匹配的方法围橡,如果有返回這個(gè)接口發(fā)方法的直接引用,查找結(jié)束缕贡。
  4. 否則翁授,宣告查找失敗,拋出 java.lang.NoSuchMethodError 異常晾咪。
    由于接口方法都是 public 修飾的收擦,因此不需要進(jìn)行訪問權(quán)限判斷。

初始化

在整個(gè)類加載的過程中谍倦,除了加載階段用戶應(yīng)用程序可以自定義類加載器進(jìn)行控制炬守,其余的階段都是有虛擬機(jī)主導(dǎo)完成的。到了初始化階段才真正開始執(zhí)行類中定義的 Java 程序代碼(或者說是字節(jié)碼)剂跟。

在準(zhǔn)備階段類變量已經(jīng)賦過一次初始值。在初始化階段虛擬機(jī)會(huì)根據(jù)程序員的代碼去初始化類變量和其他資源酣藻〔芮ⅲ或者說初始化的過程是執(zhí)行 <clinit>() 方法的過程

<clinit>() 方法是由編譯器按照源文件中出現(xiàn)的順序辽剧,自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語句塊中的語句合并而成的送淆。靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的變量,定義在他之后的變量只能賦值不能讀取怕轿。

// 非法向前引用變量代碼示例
public Class Test{
   static {
       i = 0;  //靜態(tài)塊中給后面的類變量賦值可以通過編譯
       System.out.println(i);//靜態(tài)塊中讀取定義在后面的類變量編譯器會(huì)提示「非法向前引用」
   }
   static int i = 1;
}

<clinit>() 方法和類的構(gòu)造函數(shù)(<init>() 方法)不同偷崩,不需要顯示地調(diào)用父類構(gòu)造器辟拷,虛擬機(jī)會(huì)保證子類的 <clinit>() 方法執(zhí)行前父類的該方法已經(jīng)執(zhí)行完畢。因此阐斜,虛擬機(jī)中第一個(gè)執(zhí)行的 <clinit>() 方法一定是 java.lang.Object 類衫冻。

由于父類的 <clinit>() 方法先執(zhí)行,則父類定義的靜態(tài)代碼塊先于子類的變量賦值操作谒出。

如果一個(gè)類中沒有靜態(tài)語句塊隅俘,也沒有對(duì)類變量的賦值操作,那么編譯器可以不為這個(gè)類生成 <clinit>() 方法笤喳。

虛擬機(jī)會(huì)保證一個(gè)類的 <clinit>() 方法在多線程環(huán)境中被正確的加鎖为居、同步,如果有多個(gè)線程去初始化同一個(gè)類杀狡,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的 <clinit>() 方法蒙畴,其他線程都需要阻塞等待。如果一個(gè)類的 <clinit>() 方法中存在耗時(shí)很長(zhǎng)的操作呜象,很可能造成多線程阻塞膳凝。同一個(gè)類加載器只會(huì)加載一次類,因此多線程下只會(huì)執(zhí)行一次 <clinit>() 方法董朝。

類加載器

在虛擬機(jī)外部鸠项,把實(shí)現(xiàn)通過一個(gè)類的全限定名來獲取這個(gè)類的二進(jìn)制字節(jié)流的動(dòng)作的代碼模塊成為類加載器。類加載器在類層次劃分子姜、OSGi祟绊、熱部署、代碼加密等領(lǐng)域大放異彩哥捕,成為 Java 技術(shù)體系中的重要基石牧抽。

類與類加載器

在 Java 虛擬機(jī)中,任一一個(gè)類的唯一性是由該類與其類加載器共同確立的遥赚。也就是說扬舒,同一個(gè)類在同一個(gè)虛擬機(jī)中,但在不同的類加載器中凫佛,那這兩個(gè)類則不相等讲坎。「相等」的判斷依據(jù)是 Class 對(duì)象的 equals() 方法愧薛、isAssignableFrom() 方法晨炕、isInstance() 方法的返回結(jié)果,也包括 instanceof 關(guān)鍵字對(duì)對(duì)象所屬關(guān)系的判定等情況毫炉。在使用自定義類加載器時(shí)需要注意這點(diǎn)瓮栗。

雙親委派模型

從 Java 開發(fā)人員的角度看,系統(tǒng)提供的類加載器可劃分為以下 3 種:

  1. 啟動(dòng)類加載器(Bootstrap ClassLoader):這個(gè)類負(fù)責(zé)將存放在 <JAVA_HOME>\lib 目錄中的或者被 -Xbootclasspath 參數(shù)指定的路徑中指定名稱(例如 rt.jar)的類庫。啟動(dòng)類加載器無法被 Java 程序直接引用费奸,在自定義類加載器中弥激,如果需要啟動(dòng)類加載器來加載類,在需要傳入 ClassLoader 做參數(shù)的方法中直接把 null 作為程序的類加載器代替即可愿阐。例如微服,在如下方法的第 3 個(gè)參數(shù)傳 null 即可。
public static Class<?> forName(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException{
  …
}
  1. 擴(kuò)展類加載器(Extension ClassLoader):這個(gè)加載器由 sun.misc.Launcher$ExtClassLoader 實(shí)現(xiàn)换况,負(fù)責(zé)加載 <JAVA_HOME>\lib\ext 目錄中的职辨,或者被 java.ext.dirs 系統(tǒng)變量所指定的路徑中的所有類庫,開發(fā)者可以直接使用擴(kuò)展類加載器戈二。
  2. 應(yīng)用程序類加載器(Application ClassLoader):這個(gè)類加載器由 sun.misc.Launcher$AppClassLoader 實(shí)現(xiàn)舒裤。這個(gè)類加載器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,所以一般也成為系統(tǒng)類加載器觉吭。負(fù)責(zé)加載用戶類路徑(classpath)上所指定的類庫腾供,開發(fā)者可以直接使用。如果應(yīng)用程序中沒有指定類加載器一般就作為默認(rèn)類加載器鲜滩。

此外伴鳖,我們也可以自己定義的類加載器。這些類加載器關(guān)系一般如下圖所示:

類加載器雙親委派模型

雙親委派模型并不是強(qiáng)制性的約束模型徙硅,而是一種 Java 設(shè)計(jì)者推薦的類加載器實(shí)現(xiàn)方式榜聂。雙親委派模型工作過程是:當(dāng)某個(gè)類加載器收到加載類請(qǐng)求,首先會(huì)把這個(gè)請(qǐng)求委派給父類加載器去完成嗓蘑,每一層都如此须肆,直到啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋無法加載時(shí)才會(huì)交由子類加載器去加載桩皿。

破壞雙親委派模型

  • JNDI 服務(wù)需要加載 SPI 提供的代碼

雙親委派模型很好的解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問題(基礎(chǔ)類都是上層類加載器加載的)豌汇,但如果基礎(chǔ)類想要調(diào)用用戶代碼就無法實(shí)現(xiàn)。典型的場(chǎng)景是 JNDI 服務(wù)(Java Naming and Directory Interface泄隔,Java 命名和目錄接口)拒贱,JNDI 的目的是對(duì)資源進(jìn)行集中管理和查找,它需要由獨(dú)立廠商實(shí)現(xiàn)并部署在應(yīng)用程序的 Classpath 下的 JNDI 接口提供者(SPI佛嬉,Service Provider Interface)的代碼逻澳。但啟動(dòng)類加載器不能加載這些代碼!怎么辦暖呕?

Java 通過線程上下文類加載(Thread Context ClassLoader)這個(gè)類加載器斜做,可以再 java.lang.Thread 類的 setContextClassLoader() 方法進(jìn)行設(shè)置,如果線程沒有設(shè)置則從其父線程中繼承一個(gè)缰揪,如果應(yīng)用程序沒有全局都沒有設(shè)置過,則這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器。

JNDI 服務(wù)可以使用線程上下文類加載 SPI 代碼钝腺,也就是父類加載器請(qǐng)求子類加載器去完成類加載動(dòng)作抛姑,這打破了雙親委派模型的類層次結(jié)構(gòu)。Java 中所有涉及 SPI 的加載動(dòng)作基本都采用了這種方式艳狐,例如 JNDI定硝、JDBC、JCE毫目、JAXB 和 JBI 等蔬啡。

  • 代碼熱替換(HotSwap)、模塊熱部署(Hot Deployment)

開發(fā)者對(duì)程序動(dòng)態(tài)性的追求一直十分火熱镀虐,希望應(yīng)用能像鼠標(biāo)在電腦上熱拔插一樣箱蟆,即插即用,不用重啟電腦刮便。對(duì)應(yīng)軟件開發(fā)上是希望不用重啟應(yīng)用程序即可完成發(fā)布空猜,熱部署對(duì)企業(yè)級(jí)軟件開發(fā)者有很大吸引力。OSGi 是目前 Java 業(yè)界的模塊化標(biāo)準(zhǔn)恨旱,它實(shí)現(xiàn)模塊化熱部署的關(guān)鍵原則是自定義類加載器的實(shí)現(xiàn)辈毯。每一個(gè)模塊都有一個(gè)類加載器,當(dāng)需要更換一個(gè)模塊時(shí)搜贤,連同類加載器一起換掉以實(shí)現(xiàn)熱替換谆沃。OSGi 的類加載器結(jié)構(gòu)不是雙親委派模型那樣的樹形結(jié)構(gòu),而發(fā)展成更為復(fù)雜的網(wǎng)狀結(jié)構(gòu)仪芒。

OSGi 中類加載器的使用時(shí)很值得學(xué)習(xí)的唁影,弄懂了 OSGi的實(shí)現(xiàn),就可以算掌握了類加載器的精髓桌硫。

小結(jié)

本章作者介紹了類加載過程的「加載」夭咬、「驗(yàn)證」、「準(zhǔn)備」铆隘、「解析」和「初始化
」5 個(gè)階段中虛擬機(jī)的動(dòng)作卓舵,還介紹了類加載器的工作原理以及對(duì)虛擬機(jī)的意義。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末膀钠,一起剝皮案震驚了整個(gè)濱河市掏湾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肿嘲,老刑警劉巖融击,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雳窟,居然都是意外死亡尊浪,警方通過查閱死者的電腦和手機(jī)匣屡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拇涤,“玉大人捣作,你說我怎么就攤上這事《焓浚” “怎么了券躁?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掉盅。 經(jīng)常有香客問我也拜,道長(zhǎng),這世上最難降的妖魔是什么趾痘? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任慢哈,我火速辦了婚禮,結(jié)果婚禮上扼脐,老公的妹妹穿的比我還像新娘岸军。我一直安慰自己,他們只是感情好瓦侮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布艰赞。 她就那樣靜靜地躺著,像睡著了一般肚吏。 火紅的嫁衣襯著肌膚如雪方妖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天罚攀,我揣著相機(jī)與錄音党觅,去河邊找鬼。 笑死斋泄,一個(gè)胖子當(dāng)著我的面吹牛杯瞻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炫掐,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼魁莉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了募胃?” 一聲冷哼從身側(cè)響起旗唁,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎痹束,沒想到半個(gè)月后检疫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祷嘶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年屎媳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夺溢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烛谊,死狀恐怖企垦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晒来,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布郑现,位于F島的核電站湃崩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏接箫。R本人自食惡果不足惜攒读,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辛友。 院中可真熱鬧薄扁,春花似錦、人聲如沸废累。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邑滨。三九已至日缨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掖看,已是汗流浹背匣距。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哎壳,地道東北人毅待。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像归榕,于是被迫代替她去往敵國(guó)和親尸红。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容