前言
類(lèi)加載技術(shù)是java運行的核心部分之一,雖然對于開(kāi)發(fā)來(lái)說(shuō)運用到此技術(shù)的地方不多,但是作為JAVAEE防盜版技術(shù)的組成部分之一,這一部分對于研發(fā)來(lái)說(shuō)也需要著(zhù)重了解。
本文分析對象針對于:JDK1.7
JVM預定義的三種類(lèi)加載器
1.啟動(dòng)類(lèi)加載器:?jiǎn)?dòng)類(lèi)裝入器是用本地代碼實(shí)現的類(lèi)裝入器,它負責將JRE/lib下面的核心類(lèi)庫或-Xbootclasspath選項指定的jar包加載到內存中。由于其涉及到虛擬機本地實(shí)現細節,開(kāi)發(fā)者無(wú)法直接獲取到啟動(dòng)類(lèi)加載器的引用。
2.擴展類(lèi)加載器:擴展類(lèi)加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現的。它負責將JRE/lib/ext或者由系統變量-Djava.ext.dir指定位置中的類(lèi)庫加載到內存中。開(kāi)發(fā)者可以直接使用標準擴展類(lèi)加載器。
3.系統類(lèi)加載器:系統類(lèi)加載器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現的。它負責將系統類(lèi)路徑java -classpath或-Djava.class.path變量所指的目錄下的類(lèi)庫加載到內存中。開(kāi)發(fā)者可以直接使用系統類(lèi)加載器。
類(lèi)加載的雙親委派機制
JVM在加載類(lèi)時(shí)默認采用的是雙親委派機制。通俗的講,就是某個(gè)特定的類(lèi)加載器在接到加載類(lèi)的請求時(shí),首先將加載任務(wù)委托給父類(lèi)加載器,依次遞歸,如果父類(lèi)加載器可以完成類(lèi)加載任務(wù),就成功返回;只有父類(lèi)加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。關(guān)于虛擬機默認的雙親委派機制,我們可以從系統類(lèi)加載器和擴展類(lèi)加載器為例作簡(jiǎn)單分析。
圖1.1 ExtClassLoader和AppClassLoader繼承結構圖
由圖1.1可以看到,擴展類(lèi)加載器和系統類(lèi)加載器的層次結構一致,并且查看源碼可知,系統類(lèi)加載器的構造器和擴展類(lèi)加載器的構造器在創(chuàng )建時(shí)最終都會(huì )調用ClassLoader的帶參構造器,并將父構造器注冊到其中(圖1.2)。
圖1.2 擴展類(lèi)加載器的構建過(guò)程,指定父類(lèi)加載器為null
由于可以看出來(lái),擴展類(lèi)加載器在構建時(shí)候就指定了父加載器,并且ClassLoader類(lèi)的parent方法權限為private,并且沒(méi)有提供setter方法。并且擴展類(lèi)加載器的父加載器被設置為了null。
同理,系統類(lèi)加載器的父加載器被設置為了ExtClassLoader(圖1.3)。
圖1.3 系統類(lèi)加載器的構建過(guò)程,指定父類(lèi)加載器為ExtClassLoader
用代碼來(lái)檢測一下:
- package com.noryar.classloader.test;
- public class ClassLoaderTest {
- public static void main(String[] args) {
- System.out.println("系統類(lèi)加載器為:"+ClassLoader.getSystemClassLoader());
- System.out.println("擴展類(lèi)加載器為:"+ClassLoader.getSystemClassLoader().getParent());
- System.out.println("啟動(dòng)類(lèi)加載器為:"+ClassLoader.getSystemClassLoader().getParent().getParent());
- }
- }
運行結果如下,符合預期:
- 系統類(lèi)加載器為:sun.misc.Launcher$AppClassLoader@1b31c23
- 擴展類(lèi)加載器為:sun.misc.Launcher$ExtClassLoader@1fc7b3a
- 啟動(dòng)類(lèi)加載器為:null
OK,說(shuō)了這么多,接下來(lái)介紹一下雙親委派機制的實(shí)現。首先我們分析一下ClassLoader這個(gè)抽象類(lèi)的幾個(gè)重要方法。
- public abstract class ClassLoader {
- // 用指定的二進(jìn)制名稱(chēng)加載類(lèi),它會(huì )通知JVM去解析類(lèi)信息。
- public Class<?> loadClass(String name) throws ClassNotFoundException {
- return loadClass(name, false);
- }
-
- // 用指定的二進(jìn)制名稱(chēng)加載類(lèi),具體的實(shí)現流程如下:
- // 1. 使用findLoadedClass(String)方法來(lái)檢測該類(lèi)是否已被加載
- // 2. 調用父加載器的loadClass(String)方法,這里就是雙親委派機制的實(shí)現邏輯
- // 3. 調用findClass來(lái)尋找類(lèi)
- protected Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- ....
- }
-
- // 使用指定的二進(jìn)制名稱(chēng)尋找類(lèi)。該應當在ClassLoader的子類(lèi)中進(jìn)行重寫(xiě),并且會(huì )被loadClass方法調用。
- protected Class<?> findClass(String name) throws ClassNotFoundException {
- throw new ClassNotFoundException(name);
- }
-
- // 將字節碼文件轉換成實(shí)體類(lèi),被findClass方法調用
- // 次方法調用本地方法,因此在開(kāi)發(fā)中無(wú)需復寫(xiě)。
- protected final Class<?> defineClass(String name, byte[] b, int off, int len,
- ProtectionDomain protectionDomain)
- throws ClassFormatError
- {
- ....
- }
- }
檢查發(fā)現ClassLoader的子類(lèi)只對loadClass方法進(jìn)行了擴展(主要是增加了一些校驗),并沒(méi)有對其調用機理做根本改變,因此,雙親委派機制的實(shí)現只要分析ClassLoader的loadClass(String name, boolean resolve)方法即可。下面是該方法的具體實(shí)現(圖1.4)。
圖1.4 ClassLoader的loadClass方法實(shí)現
綜上所述,當一個(gè)類(lèi)需要加載的時(shí)候,當前類(lèi)加載器就會(huì )一級一級的調用系統類(lèi)加載器->擴展類(lèi)加載器->啟動(dòng)類(lèi)加載器進(jìn)行加載,并且最終由啟動(dòng)類(lèi)加載器首先嘗試加載,加載失敗在給擴展類(lèi)加載器加載,失敗在給系統類(lèi)加載,失敗給當前類(lèi)加載器加載。任意一級加載成功則直接返回,如果都失敗,則拋出ClassNotFoundException異常。