java的類加載-ClassLoader

最近一個項目需求痛侍,實現基于http接口的外部jar包動態(tài)類加載朝氓。我平臺提供標準化的接口,接口的具體實現由業(yè)務方實現主届。業(yè)務方根據開發(fā)規(guī)范赵哲,實現接口后,打包成jar文件君丁,上傳至平臺上枫夺,用戶調用接口的時候,動態(tài)載入jar文件绘闷,運行結果返回橡庞。整個過程,業(yè)務方開發(fā)人員通過平臺的管理頁面配置,并上傳實現的jar包,即可把能力添加到我平臺上。
??整個項目的一個關鍵點事如何動態(tài)加載類文件,還必須實現動態(tài)更新虫溜。
??基于這一目標,對java類加載做了具體學習键袱。

類的裝載方式

首先我們來認識一下ClassLoader鹉动,程序在啟動的時候再菊,并不會一次性加載程序所要用的所有class文件,而是根據程序的需要颜曾,通過Java的類加載機制(ClassLoader)來動態(tài)加載某個 class 文件到內存當中的纠拔,從而只有 class 文件被載入到了內存之后,才能被其它 class 所引用泛豪。所以 ClassLoader 就是用來動態(tài)加載 class 文件到內存當中用的稠诲。

ClassLoader主要對類的請求提供服務,當JVM需要某類時诡曙,它根據名稱向ClassLoader要求這個類臀叙,然后由ClassLoader返回這個類的class對象。
Classloader就是負責將class加載到JVM中价卤,基于某個加載模型確定到底由誰加載劝萤,最后將 Class 字節(jié)碼重新解析成 JVM 統(tǒng)一要求的對象格式。

JAVA類裝載方式慎璧,有兩種:
1.隱式裝載床嫌,程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中胸私。
2.顯式裝載厌处,通過class.forname(),this.getClassLoader().loadClass()等方法岁疼,顯式加載需要的類阔涉。

類的裝載過程
JVM加載類的階段.png

看一下ClassLoader中l(wèi)oadClass方法的實現:
1.調用findLoaderClass查看是否已存在裝入的類。存在就直接返回class對象(java中任何類型都有對應的class對象捷绒,哪怕是void也有)瑰排。
2.findSystemClass查找本地文件系統(tǒng),根據不同的類裝載器疙驾,文件系統(tǒng)位置不一樣凶伙。沒有找到則返回ClassNotFoundException.
3.difineClass 接收以字節(jié)數組表示的類字節(jié)碼,并把它轉換成 Class 實例
4.resolveClass 鏈接一個指定的類它碎。(jvm裝載類的三個過程為load ,link,initialize)

loadClass方法的默認實現按照以下順序查找類: 調用findLoadedClass(String) 方法檢查這個類是否被加載過 使用父加載器調用 loadClass(String) 方法函荣,如果父加載器為 Null,類加載器裝載虛擬機內置的加載器調用 findClass(String) 方法裝載類扳肛, 如果傻挂,按照以上的步驟成功的找到對應的類,并且該方法接收的 resolve 參數的值為 true,那么就調用resolveClass(Class) 方法來處理類挖息。
ClassLoader 的子類最好覆蓋 findClass(String) 而不是這個方法金拒。 除非被重寫,這個方法默認在整個裝載過程中都是同步的(線程安全的)。

類裝載器

ClassLoader 在加載類時有一定的層次關系和規(guī)則绪抛,樹狀組織結構资铡。在 Java 中,有四種類型的類加載器幢码,分別為:BootStrapClassLoader笤休、ExtClassLoader、AppClassLoader 以及用戶自定義的 ClassLoader


類加載器層次結構圖.png

1.BootStrapClassLoader(引導類加載器) 處于類加載器層次結構的最高層症副,負責 sun.boot.class.path 路徑下類的加載店雅,默認為 jre/lib 目錄下的核心 API 或 -Xbootclasspath 選項指定的 jar 包。
2.ExtClassLoader (擴展類加載器)的加載路徑為 java.ext.dirs贞铣,默認為 jre/lib/ext 目錄或者 -Djava.ext.dirs 指定目錄下的 jar 包加載闹啦。
3.AppClassLoader(系統(tǒng)類加載器) 的加載路徑為 java.class.path,默認為環(huán)境變量 CLASSPATH 中設定的值辕坝。也可以通過 -classpath 選型進行指定窍奋。
4.用戶自定義 ClassLoader 可以根據用戶的需要定制自己的類加載過程,在運行期進行指定類的動態(tài)實時加載圣勒。

一般來說费变,這四種類加載器會形成一種父子關系,高層為低層的父加載器圣贸。在進行類加載時挚歧,首先會自底向上挨個檢查是否已經加載了指定類,如果已經加載則直接返回該類的引用吁峻。如果到最高層也沒有加載過指定類滑负,那么會自頂向下挨個嘗試加載,直到用戶自定義類加載器用含,如果還不能成功矮慕,就會拋出異常。

對于同一個類加載器實例來說啄骇,名字相同的類只能存在一個痴鳄,并且僅加載一次。不管該類有沒有變化缸夹,下次再需要加載時痪寻,它只是從自己的緩存中直接返回已經加載過的類引用。

類加載原理——雙親委托模型

每個ClassLoader實例都有一個父類加載器的引用(不是繼承的關系虽惭,是一個包含的關系)橡类,虛擬機內置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器,但可以用作其它ClassLoader實例的的父類加載器芽唇。
??當一個ClassLoader實例需要加載某個類時顾画,它會試圖親自搜索某個類之前,先把這個任務委托給它的父類加載器,這個過程是由上至下依次檢查的研侣,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載谱邪,如果沒加載到,則把任務轉交給Extension ClassLoader試圖加載庶诡,如果也沒加載到虾标,則轉交給App ClassLoader 進行加載,如果它也沒有加載得到的話灌砖,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網絡等URL中加載該類傀蚌。
??如果它們都沒有加載到這個類時基显,則拋出ClassNotFoundException異常。否則將這個找到的類生成一個類的定義善炫,并將它加載到內存當中撩幽,最后返回這個類在內存中的Class實例對象。

為什么要使用雙親委托這種模型呢箩艺?
因為這樣可以避免重復加載窜醉,當父親已經加載了該類的時候,就沒有必要 ClassLoader再加載一次艺谆≌ザ瑁考慮到安全因素,我們試想一下静汤,如果不使用這種委托模式琅催,那我們就可以隨時使用自定義的String來動態(tài)替代java核心api中定義的類型,這樣會存在非常大的安全隱患虫给,而雙親委托的方式藤抡,就可以避免這種情況,因為String已經在啟動時就被引導類加載器(Bootstrcp ClassLoader)加載抹估,所以用戶自定義的ClassLoader永遠也無法加載一個自己寫的String缠黍,除非你改變JDK中ClassLoader搜索類的默認算法。

但是JVM在搜索類的時候药蜻,又是如何判定兩個class是相同的呢瓷式?
JVM在判定兩個class是否相同時,不僅要判斷兩個類名是否相同谷暮,而且要判斷是否由同一個類加載器實例加載的蒿往。只有兩者同時滿足的情況下,JVM才認為這兩個class是相同的湿弦。就算兩個class是同一份class字節(jié)碼瓤漏,如果被兩個不同的ClassLoader實例所加載,JVM也會認為它們是兩個不同class。

Java裝載類使用“全盤負責委托機制”蔬充〉悖“全盤負責”是指當一個ClassLoder裝載一個類時,該類所依賴及引用的類也由這個ClassLoder載入(除非顯示的使用另外一個ClassLoder)饥漫;“委托機制”是指先委托父類裝載器尋找目標類榨呆,只有在找不到的情況下才從自己的類路徑中查找并裝載目標類。

線程上下文類加載器

每個運行中的線程都有一個成員ContextClassLoader庸队,用來在運行時動態(tài)地載入其它類积蜻,可以使用方法Thread.currentThread().setContextClassLoader(...);更改當前線程的contextClassLoader,來改變其載入類的行為彻消;也可以通過方法Thread.currentThread().getContextClassLoader()來獲得當前線程的ClassLoader竿拆。
??Java 應用運行的初始線程的上下文類加載器是系統(tǒng)類加載器。在線程中運行的代碼可以通過此類加載器來加載類和資源宾尚。

實際上丙笋,在Java應用中所有程序都運行在線程里,如果在程序中沒有手工設置過ClassLoader煌贴,對于一般的java類如下兩種方法獲得的ClassLoader通常都是同一個御板。

this.getClass.getClassLoader();
Thread.currentThread().getContextClassLoader();
方法一得到的Classloader是靜態(tài)的,表明類的載入者是誰;
方法二得到的Classloader是動態(tài)的牛郑,誰執(zhí)行(某個線程)怠肋,就是那個執(zhí)行者的Classloader。
對于單例模式的類井濒,靜態(tài)類等灶似,載入一次后,這個實例會被很多程序(線程)調用瑞你,對于這些類酪惭,載入的Classloader和執(zhí)行線程的Classloader通常都不同。

WEB容器類加載器

對于運行在 Java EE容器中的 Web 應用來說者甲,類加載器的實現方式與一般的 Java 應用有所不同春感。不同的 Web 容器的實現方式也會有所不同。
??以 Apache Tomcat 來說虏缸,每個 Web 應用都有一個對應的類加載器實例鲫懒。該類加載器也使用代理模式,所不同的是它是首先嘗試去加載某個類刽辙,如果找不到再代理給父類加載器窥岩。這與一般類加載器的順序是相反的。這是 Java Servlet 規(guī)范中的推薦做法宰缤,其目的是使得 Web 應用自己的類的優(yōu)先級高于 Web 容器提供的類颂翼。
??這種代理模式的一個例外是:Java 核心庫的類是不在查找范圍之內的晃洒。這也是為了保證 Java 核心庫的類型安全。

對于WEB APP線程朦乏,它的contextClassLoader是WebAppClassLoader
對于Tomcat Server線程球及,它的contextClassLoader是CatalinaClassLoader

自定義類加載器

默認的classloader只能加載指定目錄下的jar和class,如果想加載其它位置的類或jar時呻疹,需要定義自己的classloader
定義自已的類加載器分為兩步:
1吃引、繼承java.lang.ClassLoader
2、重寫父類的findClass方法

文件類加載器
??加載存儲在文件系統(tǒng)上的 Java 字節(jié)代碼刽锤。
??類 FileSystemClassLoader繼承自類java.lang.ClassLoader镊尺。為了保證類加載器都正確實現代理模式,在開發(fā)自己的類加載器時并思,最好不要覆寫 loadClass()方法鹅心,而是覆寫findClass()方法。
??類 FileSystemClassLoader的 findClass()方法首先根據類的全名在硬盤上查找類的字節(jié)代碼文件(.class 文件)纺荧,然后讀取該文件內容,最后通過 defineClass()方法來把這些字節(jié)代碼轉換成 java.lang.Class類的實例颅筋。

public class FileSystemClassLoader extends ClassLoader
{
    private String rootDir;

    public FileSystemClassLoader(String rootDir){
        this.rootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null){
            throw new ClassNotFoundException();
        }
        else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1){
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String classNameToPath(String className) {
        return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
    }
}

網絡類加載器
??一個網絡類加載器來說明如何通過類加載器來實現組件的動態(tài)更新宙暇。即基本的場景是:Java 字節(jié)代碼(.class)文件存放在服務器上,客戶端通過網絡的方式獲取字節(jié)代碼并執(zhí)行议泵。當有版本更新的時候占贫,只需要替換掉服務器上保存的文件即可。通過類加載器可以比較簡單的實現這種需求先口。
??類 NetworkClassLoader 負責通過網絡下載 Java 類字節(jié)代碼并定義出 Java 類型奥。它的實現與FileSystemClassLoader 類似。在通過 NetworkClassLoader 加載了某個版本的類之后碉京,一般有兩種做法來使用它厢汹。第一種做法是使用 Java 反射 API。另外一種做法是使用接口谐宙。

OSGI動態(tài)模塊系統(tǒng)

osgi是java動態(tài)化模塊系統(tǒng)烫葬,動態(tài)模塊部署。面向服務和基于組件的運行環(huán)境凡蜻。組件盡可能解耦搭综,能讓組件動態(tài)的發(fā)現其他組件。
這個有興趣也可以去研究關注一下划栓。

關于《java類的熱替換》參考:
https://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末兑巾,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子忠荞,更是在濱河造成了極大的恐慌蒋歌,老刑警劉巖帅掘,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異奋姿,居然都是意外死亡锄开,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門称诗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萍悴,“玉大人,你說我怎么就攤上這事寓免⊙⒂眨” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵袜香,是天一觀的道長撕予。 經常有香客問我,道長实抡,這世上最難降的妖魔是什么吆寨? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任俺孙,我火速辦了婚禮荣茫,結果婚禮上计露,老公的妹妹穿的比我還像新娘票罐。我一直安慰自己,他們只是感情好蚕礼,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冀痕,像睡著了一般宵距。 火紅的嫁衣襯著肌膚如雪婿斥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天坟桅,我揣著相機與錄音蓬戚,去河邊找鬼豫喧。 笑死缕棵,一個胖子當著我的面吹牛篙程,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼前塔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起迁霎,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后誓军,有當地人在樹林里發(fā)現了一具尸體评肆,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡征绸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了管怠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淆衷。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渤弛,靈堂內的尸體忽然破棺而出祝拯,到底是詐尸還是另有隱情,我是刑警寧澤她肯,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布佳头,位于F島的核電站,受9級特大地震影響晴氨,放射性物質發(fā)生泄漏康嘉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一籽前、第九天 我趴在偏房一處隱蔽的房頂上張望亭珍。 院中可真熱鬧,春花似錦枝哄、人聲如沸肄梨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至财松,卻和暖如春瘪贱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辆毡。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工菜秦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舶掖。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓球昨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親眨攘。 傳聞我的和親對象是個殘疾皇子主慰,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容

  • JAVA類裝載方式嚣州,有兩種: 1.隱式裝載, 程序在運行過程中當碰到通過new 等方式生成對象時共螺,隱式調用類裝載器...
    代碼之尖閱讀 651評論 0 48
  • JAVA類裝載方式该肴,有兩種 隱式裝載:程序在運行過程中當碰到通過new 等方式生成對象時,隱式調用類裝載器加載對應...
    yincb閱讀 599評論 0 2
  • JVM類加載器ClassLoader JAVA類裝載方式 1.隱式裝載藐不, 程序在運行過程中當碰到通過new 等方式...
    步二小哥閱讀 431評論 0 1
  • 類加載器是 Java 語言的一個創(chuàng)新匀哄,也是 Java 語言流行的重要原因之一。它使得 Java 類可以被動態(tài)加載到...
    CHSmile閱讀 1,596評論 0 12
  • ClassLoader翻譯過來就是類加載器雏蛮,普通的java開發(fā)者其實用到的不多涎嚼,但對于某些框架開發(fā)者來說卻非常常見...
    時待吾閱讀 1,060評論 0 1