5、java類(lèi)加載器ClassLoader源碼簡(jiǎn)析

1. ClassLoader源碼

??Java中的所有類(lèi)窥浪,必須被裝載到j(luò)vm中才能運(yùn)行刚陡,類(lèi)裝載器把類(lèi)文件從硬盤(pán)讀取到內(nèi)存中唆鸡,JVM在加載類(lèi)的時(shí)候趾诗,都是通過(guò)ClassLoader的loadClass()來(lái)加載class的痹届,loadClass使用雙親委派模式贮尖。

ClassLoader抽象類(lèi):

public abstract class ClassLoader

ClassLoader類(lèi)是一個(gè)抽象類(lèi)笛粘,sun公司是這么解釋這個(gè)類(lèi)的:

class loader是一個(gè)負(fù)責(zé)加載classes的對(duì)象,ClassLoader類(lèi)是一個(gè)抽象類(lèi),需要給出類(lèi)的二進(jìn)制名稱薪前,class loader嘗試定位或者產(chǎn)生一個(gè)class的數(shù)據(jù)润努,一個(gè)典型的策略是把二進(jìn)制名字轉(zhuǎn)換成文件名然后到文件系統(tǒng)中找到該文件。

loadClass():

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

??使用指定的二進(jìn)制名稱(name)來(lái)加載類(lèi)示括,默認(rèn)實(shí)現(xiàn)按照以下順序查找類(lèi):

  • 1.getClassLoadingLock(name)獲取本實(shí)例的鎖
  • 1.調(diào)用findLoadedClass(String)方法檢查這個(gè)類(lèi)是否被加載過(guò)
  • 2.使用父加載器調(diào)用loadClass(String)方法
  • 3.如果父加載器為Null铺浇,類(lèi)加載器裝載虛擬機(jī)內(nèi)置的加載器調(diào)用findClass(String)方法裝載類(lèi)
  • 4.如果,按照以上的步驟成功的找到對(duì)應(yīng)的類(lèi)垛膝,并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來(lái)處理類(lèi)鳍侣。
  • 5.ClassLoader的子類(lèi)最好覆蓋findClass(String)而不是這個(gè)方法。 除非被重寫(xiě)吼拥,這個(gè)方法默認(rèn)在整個(gè)裝載過(guò)程中都是同步的(線程安全的).

1.1 synchronized (getClassLoadingLock(name))

??這是一個(gè)同步代碼塊倚聚,synchronized的括號(hào)中放的應(yīng)該是一個(gè)對(duì)象,getClassLoadingLock(name)方法:

protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

??根據(jù)變量parallelLockMap的值進(jìn)行不同的操作凿可,如果這個(gè)變量是Null則直接返回this惑折,如果這個(gè)屬性不為Null則新建一個(gè)對(duì)象,然后在調(diào)用一個(gè)putIfAbsent(className, newLock);方法來(lái)給剛剛創(chuàng)建好的對(duì)象賦值枯跑,這個(gè)方法的作用我們一會(huì)講惨驶。而parallelLockMap變量又是ClassLoader類(lèi)的成員變量:

private final ConcurrentHashMap<String, Object> parallelLockMap;

??parallelLockMap的初始化是在ClassLoader的構(gòu)造方法里做的:

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }

??構(gòu)造函數(shù)根據(jù)一個(gè)屬性ParallelLoaders的Registered狀態(tài)的不同來(lái)給parallelLockMap賦值。ParallelLoaders又是在哪賦值的呢敛助?在ClassLoader類(lèi)中包含一個(gè)靜態(tài)內(nèi)部類(lèi)private static class ParallelLoaders粗卜,在ClassLoader被加載的時(shí)候這個(gè)靜態(tài)內(nèi)部類(lèi)就被初始化。

private static class ParallelLoaders {
        private ParallelLoaders() {}

        // the set of parallel capable loader types
        private static final Set<Class<? extends ClassLoader>> loaderTypes =
            Collections.newSetFromMap(
                new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
        static {
            synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
        }

        /**
         * Registers the given class loader type as parallel capabale.
         * Returns {@code true} is successfully registered; {@code false} if
         * loader's super class is not registered.
         */
        static boolean register(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                if (loaderTypes.contains(c.getSuperclass())) {
                    // register the class loader as parallel capable
                    // if and only if all of its super classes are.
                    // Note: given current classloading sequence, if
                    // the immediate super class is parallel capable,
                    // all the super classes higher up must be too.
                    loaderTypes.add(c);
                    return true;
                } else {
                    return false;
                }
            }
        }

        /**
         * Returns {@code true} if the given class loader type is
         * registered as parallel capable.
         */
        static boolean isRegistered(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                return loaderTypes.contains(c);
            }
        }
    }

??這個(gè)靜態(tài)類(lèi)ParallelLoaders封裝了并行的可裝載的類(lèi)型的集合纳击。

- 首先,在ClassLoader類(lèi)中有一個(gè)靜態(tài)內(nèi)部類(lèi)ParallelLoaders评疗,他會(huì)指定的類(lèi)的并行能力测砂。
- 如果當(dāng)前的加載器被定位為具有并行能力,那么他就給parallelLockMap定義百匆,就是new一個(gè) ConcurrentHashMap<>()砌些,那么這個(gè)時(shí)候,我們知道如果當(dāng)前的加載器是具有并行能力的加匈,那么parallelLockMap就不是Null存璃。
- 這個(gè)時(shí)候,我們判斷parallelLockMap是不是Null雕拼,如果他是null纵东,說(shuō)明該加載器沒(méi)有注冊(cè)并行能力,那么我們沒(méi)有必要給他一個(gè)加鎖的對(duì)象啥寇,getClassLoadingLock方法直接返回this,就是當(dāng)前的加載器的一個(gè)實(shí)例偎球。
- 如果這個(gè)parallelLockMap不是null洒扎,那就說(shuō)明該加載器是有并行能力的,那么就可能有并行情況衰絮,那就需要返回一個(gè)鎖對(duì)象袍冷。然后就是創(chuàng)建一個(gè)新的Object對(duì)象,調(diào)用parallelLockMap的putIfAbsent(className, newLock)方法猫牡。
- putIfAbsent(className, newLock)的作用是:首先根據(jù)傳進(jìn)來(lái)的className,檢查該名字是否已經(jīng)關(guān)聯(lián)了一個(gè)value值胡诗,如果已經(jīng)關(guān)聯(lián)過(guò)value值,那么直接把他關(guān)聯(lián)的值返回淌友,如果沒(méi)有關(guān)聯(lián)過(guò)值的話煌恢,那就把我們傳進(jìn)來(lái)的Object對(duì)象作為value值,className作為Key值組成一個(gè)map返回震庭。然后無(wú)論putIfAbsent方法的返回值是什么瑰抵,都把它賦值給我們剛剛生成的那個(gè)Object對(duì)象。 
- 這個(gè)時(shí)候归薛,我們來(lái)簡(jiǎn)單說(shuō)明一下getClassLoadingLock(String className)的作用,就是: 為類(lèi)的加載操作返回一個(gè)鎖對(duì)象匪蝙。為了向后兼容主籍,這個(gè)方法這樣實(shí)現(xiàn):如果當(dāng)前的classloader對(duì)象注冊(cè)了并行能力,方法返回一個(gè)與指定的名字className相關(guān)聯(lián)的特定對(duì)象逛球,否則千元,直接返回當(dāng)前的ClassLoader對(duì)象。

1.2 findLoadedClass(name)

??在加載類(lèi)之前先調(diào)用findLoadedClass方法檢查該類(lèi)是否已經(jīng)被加載過(guò)颤绕,findLoadedClass會(huì)返回一個(gè)Class類(lèi)型的對(duì)象幸海,如果該類(lèi)已經(jīng)被加載過(guò),那么就可以直接返回該對(duì)象(在返回之前會(huì)根據(jù)resolve的值來(lái)決定是否處理該對(duì)象奥务,具體的怎么處理后面會(huì)講)物独。 如果,該類(lèi)沒(méi)有被加載過(guò)氯葬,那么執(zhí)行以下的加載過(guò)程挡篓。

try {
    if (parent != null) {
           c = parent.loadClass(name, false);
    } else {
            c = findBootstrapClassOrNull(name);
     }
} catch (ClassNotFoundException e) {
         // ClassNotFoundException thrown if class not found
          // from the non-null parent class loader
}

??如果父加載器不為空,那么調(diào)用父加載器的loadClass方法加載類(lèi)帚称,如果父加載器為空官研,那么調(diào)用虛擬機(jī)的加載器來(lái)加載類(lèi)。如果以上兩個(gè)步驟都沒(méi)有成功的加載到類(lèi)闯睹,那么調(diào)用自己的findClass(name)方法來(lái)加載類(lèi):

c = findClass(name);

??這個(gè)時(shí)候戏羽,我們已經(jīng)得到了加載之后的類(lèi),那么就根據(jù)resolve的值決定是否調(diào)用resolveClass方法楼吃。resolveClass方法的作用是:

鏈接指定的類(lèi)始花。這個(gè)方法給Classloader用來(lái)鏈接一個(gè)類(lèi)妄讯,如果這個(gè)類(lèi)已經(jīng)被鏈接過(guò)了,那么這個(gè)方法只做一個(gè)簡(jiǎn)單的返回衙荐。否則捞挥,這個(gè)類(lèi)將被按照 Java?規(guī)范中的Execution描述進(jìn)行鏈接。

1.3 類(lèi)加載器

??java中的類(lèi)大致分為三種:系統(tǒng)類(lèi)忧吟、擴(kuò)展類(lèi)砌函、由程序員自定義的類(lèi)。類(lèi)裝載方式包括隱式裝載和顯式裝載溜族。隱式裝載:程序在運(yùn)行過(guò)程中當(dāng)碰到通過(guò)new 等方式生成對(duì)象時(shí)讹俊,隱式調(diào)用類(lèi)裝載器加載對(duì)應(yīng)的類(lèi)到j(luò)vm中。顯式裝載:通過(guò)class.forname()等方法煌抒,顯式加載需要的類(lèi)仍劈。

??類(lèi)加載的動(dòng)態(tài)性是指:一個(gè)應(yīng)用程序總是由n多個(gè)類(lèi)組成,Java程序啟動(dòng)時(shí)寡壮,并不是一次把所有的類(lèi)全部加載后再運(yùn)行贩疙,它總是先把保證程序運(yùn)行的基礎(chǔ)類(lèi)一次性加載到j(luò)vm中,其它類(lèi)等到j(luò)vm用到的時(shí)候再加載况既,這樣的好處是節(jié)省了內(nèi)存的開(kāi)銷(xiāo)这溅,因?yàn)閖ava最早就是為嵌入式系統(tǒng)而設(shè)計(jì)的,內(nèi)存寶貴棒仍,這是一種可以理解的機(jī)制悲靴,而用到時(shí)再加載這也是java動(dòng)態(tài)性的一種體現(xiàn)。

??Java中的類(lèi)裝載器實(shí)質(zhì)上也是類(lèi)莫其,功能是把類(lèi)載入jvm中癞尚,值得注意的是jvm的類(lèi)裝載器并不是一個(gè),而是三個(gè)乱陡,層次結(jié)構(gòu)如下:


類(lèi)加載器.png

??為什么要有三個(gè)類(lèi)加載器浇揩,一方面是分工,各自負(fù)責(zé)各自的區(qū)塊憨颠,另一方面為了實(shí)現(xiàn)委托模型临燃,下面會(huì)談到該模型。

??類(lèi)加載器的工作原理:在這里java采用了委托模型機(jī)制烙心,這個(gè)機(jī)制簡(jiǎn)單來(lái)講膜廊,就是“類(lèi)裝載器有載入類(lèi)的需求時(shí),會(huì)先請(qǐng)示其Parent使用其搜索路徑幫忙載入淫茵,如果Parent 找不到,那么才由自己依照自己的搜索路徑搜索類(lèi)”爪瓜。


類(lèi)加載順序.png

示例:

Public class Test{
   Public static void main(String[] arg){
       ClassLoader c  = Test.class.getClassLoader();  //獲取Test類(lèi)的類(lèi)加載器
       System.out.println(c); 
       ClassLoader c1 = c.getParent();  //獲取c這個(gè)類(lèi)加載器的父類(lèi)加載器
       System.out.println(c1);
       ClassLoader c2 = c1.getParent();//獲取c1這個(gè)類(lèi)加載器的父類(lèi)加載器
       System.out.println(c2);
 }
}

結(jié)果:

。匙瘪。铆铆。AppClassLoader蝶缀。。薄货。

翁都。。谅猾。ExtClassLoader柄慰。。税娜。

Null

??Test是由AppClassLoader加載器加載的坐搔,AppClassLoader的Parent 加載器是 ExtClassLoader,但是ExtClassLoader的Parent為 null 是怎么回事呵,朋友們留意的話敬矩,前面有提到Bootstrap Loader是用C++語(yǔ)言寫(xiě)的概行,依java的觀點(diǎn)來(lái)看,邏輯上并不存在Bootstrap Loader的類(lèi)實(shí)體弧岳,所以在java程序代碼里試圖打印出其內(nèi)容時(shí)凳忙,我們就會(huì)看到輸出為null。

??類(lèi)裝載器ClassLoader(一個(gè)抽象類(lèi))描述一下JVM加載class文件的原理機(jī)制禽炬。類(lèi)裝載器就是尋找類(lèi)或接口字節(jié)碼文件進(jìn)行解析并構(gòu)造JVM內(nèi)部對(duì)象表示的組件涧卵,在java中類(lèi)裝載器把一個(gè)類(lèi)裝入JVM,經(jīng)過(guò)以下步驟:

1瞎抛、裝載:查找和導(dǎo)入Class文件 
2艺演、鏈接:其中解析步驟是可以選擇的 (a)檢查:檢查載入的class文件數(shù)據(jù)的正確性 (b)準(zhǔn)備:給類(lèi)的靜態(tài)變量分配存儲(chǔ)空間 (c)解析:將符號(hào)引用轉(zhuǎn)成直接引用 3却紧、初始化:對(duì)靜態(tài)變量桐臊,靜態(tài)代碼塊執(zhí)行初始化工作
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市晓殊,隨后出現(xiàn)的幾起案子断凶,更是在濱河造成了極大的恐慌,老刑警劉巖巫俺,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件认烁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡介汹,警方通過(guò)查閱死者的電腦和手機(jī)却嗡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嘹承,“玉大人窗价,你說(shuō)我怎么就攤上這事√揪恚” “怎么了撼港?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵坪它,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我帝牡,道長(zhǎng)往毡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任靶溜,我火速辦了婚禮开瞭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墨技。我一直安慰自己惩阶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布扣汪。 她就那樣靜靜地躺著断楷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪崭别。 梳的紋絲不亂的頭發(fā)上冬筒,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音茅主,去河邊找鬼舞痰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛诀姚,可吹牛的內(nèi)容都是我干的响牛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赫段,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呀打!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起糯笙,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贬丛,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后给涕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體豺憔,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年够庙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恭应。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耘眨,死狀恐怖昼榛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情毅桃,我是刑警寧澤褒纲,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布准夷,位于F島的核電站,受9級(jí)特大地震影響莺掠,放射性物質(zhì)發(fā)生泄漏衫嵌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一彻秆、第九天 我趴在偏房一處隱蔽的房頂上張望楔绞。 院中可真熱鬧,春花似錦唇兑、人聲如沸酒朵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蔫耽。三九已至,卻和暖如春留夜,著一層夾襖步出監(jiān)牢的瞬間匙铡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工碍粥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鳖眼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓嚼摩,卻偏偏與公主長(zhǎng)得像钦讳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枕面,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360