5、java類加載器ClassLoader源碼簡析

1. ClassLoader源碼

??Java中的所有類,必須被裝載到j(luò)vm中才能運(yùn)行,類裝載器把類文件從硬盤讀取到內(nèi)存中,JVM在加載類的時候,都是通過ClassLoader的loadClass()來加載class的,loadClass使用雙親委派模式。

ClassLoader抽象類:

public abstract class ClassLoader

ClassLoader類是一個抽象類,sun公司是這么解釋這個類的:

class loader是一個負(fù)責(zé)加載classes的對象,ClassLoader類是一個抽象類,需要給出類的二進(jìn)制名稱,class loader嘗試定位或者產(chǎn)生一個class的數(shù)據(jù),一個典型的策略是把二進(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)來加載類,默認(rèn)實(shí)現(xiàn)按照以下順序查找類:

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

1.1 synchronized (getClassLoadingLock(name))

??這是一個同步代碼塊,synchronized的括號中放的應(yīng)該是一個對象,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)行不同的操作,如果這個變量是Null則直接返回this,如果這個屬性不為Null則新建一個對象,然后在調(diào)用一個putIfAbsent(className, newLock);方法來給剛剛創(chuàng)建好的對象賦值,這個方法的作用我們一會講。而parallelLockMap變量又是ClassLoader類的成員變量:

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ù)一個屬性ParallelLoaders的Registered狀態(tài)的不同來給parallelLockMap賦值。ParallelLoaders又是在哪賦值的呢?在ClassLoader類中包含一個靜態(tài)內(nèi)部類private static class ParallelLoaders,在ClassLoader被加載的時候這個靜態(tài)內(nè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);
            }
        }
    }

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

- 首先,在ClassLoader類中有一個靜態(tài)內(nèi)部類ParallelLoaders,他會指定的類的并行能力。
- 如果當(dāng)前的加載器被定位為具有并行能力,那么他就給parallelLockMap定義,就是new一個 ConcurrentHashMap<>(),那么這個時候,我們知道如果當(dāng)前的加載器是具有并行能力的,那么parallelLockMap就不是Null。
- 這個時候,我們判斷parallelLockMap是不是Null,如果他是null,說明該加載器沒有注冊并行能力,那么我們沒有必要給他一個加鎖的對象,getClassLoadingLock方法直接返回this,就是當(dāng)前的加載器的一個實(shí)例。
- 如果這個parallelLockMap不是null,那就說明該加載器是有并行能力的,那么就可能有并行情況,那就需要返回一個鎖對象。然后就是創(chuàng)建一個新的Object對象,調(diào)用parallelLockMap的putIfAbsent(className, newLock)方法。
- putIfAbsent(className, newLock)的作用是:首先根據(jù)傳進(jìn)來的className,檢查該名字是否已經(jīng)關(guān)聯(lián)了一個value值,如果已經(jīng)關(guān)聯(lián)過value值,那么直接把他關(guān)聯(lián)的值返回,如果沒有關(guān)聯(lián)過值的話,那就把我們傳進(jìn)來的Object對象作為value值,className作為Key值組成一個map返回。然后無論putIfAbsent方法的返回值是什么,都把它賦值給我們剛剛生成的那個Object對象。 
- 這個時候,我們來簡單說明一下getClassLoadingLock(String className)的作用,就是: 為類的加載操作返回一個鎖對象。為了向后兼容,這個方法這樣實(shí)現(xiàn):如果當(dāng)前的classloader對象注冊了并行能力,方法返回一個與指定的名字className相關(guān)聯(lián)的特定對象,否則,直接返回當(dāng)前的ClassLoader對象。

1.2 findLoadedClass(name)

??在加載類之前先調(diào)用findLoadedClass方法檢查該類是否已經(jīng)被加載過,findLoadedClass會返回一個Class類型的對象,如果該類已經(jīng)被加載過,那么就可以直接返回該對象(在返回之前會根據(jù)resolve的值來決定是否處理該對象,具體的怎么處理后面會講)。 如果,該類沒有被加載過,那么執(zhí)行以下的加載過程。

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方法加載類,如果父加載器為空,那么調(diào)用虛擬機(jī)的加載器來加載類。如果以上兩個步驟都沒有成功的加載到類,那么調(diào)用自己的findClass(name)方法來加載類:

c = findClass(name);

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

鏈接指定的類。這個方法給Classloader用來鏈接一個類,如果這個類已經(jīng)被鏈接過了,那么這個方法只做一個簡單的返回。否則,這個類將被按照 Java?規(guī)范中的Execution描述進(jìn)行鏈接。

1.3 類加載器

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

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

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


類加載器.png

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

??類加載器的工作原理:在這里java采用了委托模型機(jī)制,這個機(jī)制簡單來講,就是“類裝載器有載入類的需求時,會先請示其Parent使用其搜索路徑幫忙載入,如果Parent 找不到,那么才由自己依照自己的搜索路徑搜索類”。


類加載順序.png

示例:

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

結(jié)果:

。。。AppClassLoader。。。

。。。ExtClassLoader。。。

Null

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

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

1、裝載:查找和導(dǎo)入Class文件 
2、鏈接:其中解析步驟是可以選擇的 (a)檢查:檢查載入的class文件數(shù)據(jù)的正確性 (b)準(zhǔn)備:給類的靜態(tài)變量分配存儲空間 (c)解析:將符號引用轉(zhuǎn)成直接引用 3、初始化:對靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化工作
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容