Java ClassLoader機(jī)制(源碼級別) —從內(nèi)部類單例模式看

內(nèi)部類單例是種很好的單例模式,
利用ClassLoader 線程安全的加載模式

image

為了更好的理解類的加載機(jī)制,我們來深入研究一下ClassLoader和他的loadClass()方法。


類加載過程

Java中的所有類,必須被裝載到j(luò)vm中才能運(yùn)行,這個(gè)裝載工作是由jvm中的類裝載器完成的,類裝載器所做的工作實(shí)質(zhì)是把類文件從硬盤讀取到內(nèi)存中,JVM在加載類的時(shí)候,都是通過ClassLoader的loadClass()方法來加載class的,loadClass使用雙親委派模式。

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


public abstract class ClassLoader{
    
    .....
}

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}    


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;
        }
    }

load大致意思

使用指定的二進(jìn)制名稱來加載類,這個(gè)方法的默認(rèn)實(shí)現(xiàn)按照以下順序查找類: 調(diào)用findLoadedClass(String)方法檢查這個(gè)類是否被加載過 使用父加載器調(diào)用loadClass(String)方法,如果父加載器為Null,類加載器裝載虛擬機(jī)內(nèi)置的加載器調(diào)用findClass(String)方法裝載類, 如果,按照以上的步驟成功的找到對應(yīng)的類,并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來處理類。 ClassLoader的子類最好覆蓋findClass(String)而不是這個(gè)方法。 除非被重寫,這個(gè)方法默認(rèn)在整個(gè)裝載過程中都是同步的(線程安全的)

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;
}

private final ConcurrentHashMap<String, Object> parallelLockMap;

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;
    }
}

  • 從源碼可以看出來 如果 parallelLockMap 為Null 方法就返回本身,不為Null的話那么就新建一個(gè)對象,然后在調(diào)用一個(gè)putIfAbsent(className, newLock);方法來給剛剛創(chuàng)建好的對象賦值
  • parallelLockMap 從類的構(gòu)造方法賦值可以看到構(gòu)造函數(shù)根據(jù)一個(gè)屬性==ParallelLoaders==的==Registered==狀態(tài)的不同來給==parallelLockMap== 賦值。
  • 這個(gè)ParallelLoaders是在哪賦值的呢?是在ClassLoader類中包含一個(gè)靜態(tài)內(nèi)部類private static class ParallelLoaders,在ClassLoader被加載的時(shí)候這個(gè)靜態(tài)內(nèi)部類就被初始化。
  • 整理一下:
在ClassLoader類中有一個(gè)靜態(tài)內(nèi)部類ParallelLoaders,他會指定的類的并行能力,如果當(dāng)前的加載器被定位為具有并行能力,
那么他就給parallelLockMap定義,就是new一個(gè)ConcurrentHashMap<>(),那么這個(gè)時(shí)候,
我們知道如果當(dāng)前的加載器是具有并行能力的,那么parallelLockMap就不是Null,這個(gè)時(shí)候,我們判斷parallelLockMap是不是Null,
如果他是null,說明該加載器沒有注冊并行能力,那么我們沒有必要給他一個(gè)加鎖的對象,getClassLoadingLock方法直接返回this,就是當(dāng)前的加載器的一個(gè)實(shí)例。
如果這個(gè)parallelLockMap不是null,那就說明該加載器是有并行能力的,那么就可能有并行情況,那就需要返回一個(gè)鎖對象。
然后就是創(chuàng)建一個(gè)新的Object對象,調(diào)用parallelLockMap的putIfAbsent(className, newLock)方法,
這個(gè)方法的作用是:首先根據(jù)傳進(jìn)來的className,檢查該名字是否已經(jīng)關(guān)聯(lián)了一個(gè)value值,如果已經(jīng)關(guān)聯(lián)過value值,
那么直接把他關(guān)聯(lián)的值返回,如果沒有關(guān)聯(lián)過值的話,那就把我們傳進(jìn)來的Object對象作為value值,className作為Key值組成一個(gè)map返回。
然后無論putIfAbsent方法的返回值是什么,都把它賦值給我們剛剛生成的那個(gè)Object對象。
  • 我們來簡單說明一下getClassLoadingLock(String className)的作用,就是: 為類的加載操作返回一個(gè)鎖對象。為了向后兼容,這個(gè)方法這樣實(shí)現(xiàn):如果當(dāng)前的classloader對象注冊了并行能力,方法返回一個(gè)與指定的名字className相關(guān)聯(lián)的特定對象,否則,直接返回當(dāng)前的ClassLoader對象。
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ī)的加載器來加載類。

if (c == null) {
    // If still not found, then invoke findClass in order
    // to find the class.
    //如果還沒有找到,那么按順序調(diào)用find類來找到類。
    ......
    c = findClass(name);
    ......
}

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

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

總結(jié)

  • java 類加載器
    • Bootstrp loader
      Bootstrp加載器是用C++語言寫的,它是在Java虛擬機(jī)啟動后初始化的,它主要負(fù)責(zé)加載%JAVA_HOME%/jre/lib,-Xbootclasspath參數(shù)指定的路徑以及%JAVA_HOME%/jre/classes中的類。

    • ExtClassLoader
      Bootstrp loader加載ExtClassLoader,并且將ExtClassLoader的父加載器設(shè)置為Bootstrp loader.ExtClassLoader是用Java寫的,具體來說就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統(tǒng)變量指定的路徑中類庫。

    • AppClassLoader
      Bootstrp loader加載完ExtClassLoader后,就會加載AppClassLoader,并且將AppClassLoader的父加載器指定為 ExtClassLoader。AppClassLoader也是用Java寫成的,它的實(shí)現(xiàn)類是 sun.misc.Launcher$AppClassLoader,另外我們知道ClassLoader中有個(gè)getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要負(fù)責(zé)加載classpath所指定的位置的類或者是jar文檔,它也是Java程序默認(rèn)的類加載器。

image
  • 類加載器工作
image

類裝載器有載入類的需求時(shí),會先請示其Parent使用其搜索路徑幫忙載入,如果Parent 找不到,那么才由自己依照自己的搜索

類加載器 從下往上搜索 從上往下加載

  • 類裝載時(shí)機(jī)

JVM規(guī)范嚴(yán)格定義了何時(shí)需要對類進(jìn)行初始化:

  1. 通過new關(guān)鍵字、反射、clone、反序列化機(jī)制實(shí)例化對象時(shí)。
  2. 調(diào)用類的靜態(tài)方法時(shí)。
  3. 使用類的靜態(tài)字段或?qū)ζ滟x值時(shí)。
  4. 通過反射調(diào)用類的方法時(shí)。
  5. 初始化該類的子類時(shí)(初始化子類前其父類必須已經(jīng)被初始化)。
  6. JVM啟動時(shí)被標(biāo)記為啟動類的類(簡單理解為具有main方法的類)。

參考

深度分析Java的ClassLoader機(jī)制(源碼級別)
Java虛擬機(jī)結(jié)構(gòu)分析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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