ClassLoader深入學(xué)習(xí)記錄

ClassLoader是什么

  • ClassLoader是Java的類(lèi)加載機(jī)制
    ClassLoader用于動(dòng)態(tài)加載class文件到內(nèi)存中

Java程序?qū)懞煤?,是由若干個(gè)class文件組織而成的一個(gè)完整的Java應(yīng)用程序;程序運(yùn)行時(shí),會(huì)調(diào)用改程序的一個(gè)入口函數(shù)來(lái)調(diào)用系統(tǒng)的相關(guān)功能;這些功能被封裝在不同的class文件中;程序啟動(dòng)時(shí),不會(huì)一次性加載程序所需要的所有class文件,而是根據(jù)需要,通過(guò)ClassLoader來(lái)動(dòng)態(tài)加載某個(gè)class文件到內(nèi)存中;當(dāng)class文件被載入到內(nèi)存后,才能被其他class所引用;

Java提供的ClassLoader

  • Java提供的ClassLoader有三個(gè)
    BootStrap ClassLoader(啟動(dòng)類(lèi)加載器):是Java類(lèi)加載層最頂層的類(lèi)加載器,由C++編寫(xiě)而成, 已經(jīng)內(nèi)嵌到JVM中了;主要用來(lái)加載JDK的核心類(lèi)庫(kù),如:rt.jar、resources.jar、charsets.jar等;
    eg:利用以下代碼獲得BootStrap ClassLoader加載的jar或class文件
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();  
for (int i = 0; i < urls.length; i++) {  
    System.out.println(urls[i].toExternalForm());  
}  
System.out.println(System.getProperty("sun.boot.class.path"));
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/classes

Extension ClassLoader(擴(kuò)展類(lèi)加載器):負(fù)責(zé)加載Java的擴(kuò)展類(lèi)庫(kù),默認(rèn)加載JAVA_HOME/jre/lib/ext目錄下的所有jar;

App ClassLoader(擴(kuò)展系統(tǒng)類(lèi)加載器):負(fù)責(zé)加載應(yīng)用程序classpath目錄下的所有jar和class文件


CustomClassLoader:自定義ClassLoader(繼承java.lang.ClassLoader類(lèi)/Extension ClassLoader/App ClassLoader);然而,不能繼承Bootstrap ClassLoader,因?yàn)锽ootstrap ClassLoader由底層C++編寫(xiě),已遷入JVM內(nèi)核,當(dāng)JVM啟動(dòng)后,Bootstrap ClassLoader也隨之啟動(dòng),負(fù)責(zé)加載核心類(lèi)庫(kù),并后遭Extension ClassLoader和App ClassLoader類(lèi)加載器;

查看父類(lèi)加載器

/**
 * 查看父類(lèi)加載器
 */
private static void test1() {
    ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
    System.out.println("系統(tǒng)類(lèi)裝載器:" + appClassLoader);
    ClassLoader extensionClassLoader = appClassLoader.getParent();
    System.out.println("系統(tǒng)類(lèi)裝載器的父類(lèi)加載器——擴(kuò)展類(lèi)加載器:" + extensionClassLoader);
    ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
    System.out.println("擴(kuò)展類(lèi)加載器的父類(lèi)加載器——引導(dǎo)類(lèi)加載器:" + bootstrapClassLoader);
}

ExtensionClassLoader的parent為null因?yàn)閎ootstrapClassLoader不是一個(gè)普通的Java類(lèi)

ClassLoader加載類(lèi)的原理

原理

使用雙親委托模型來(lái)搜索類(lèi);每個(gè)ClassLoader實(shí)例都有一個(gè)父類(lèi)加載器的引用(是一種包含關(guān)系);而虛擬機(jī)內(nèi)置的類(lèi)加載器(Bootstrap ClassLoader)本身沒(méi)有福類(lèi)加載器,但可以用作其他的任務(wù)委托給它的福類(lèi)加載器,這個(gè)過(guò)程是自上而下一次檢查的;

首先有最頂層的類(lèi)加載器Bootstrap ClassLoader視圖加載,若沒(méi)有加載到;則將任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,若沒(méi)有加載到;則轉(zhuǎn)交給App ClassLoader進(jìn)行加載,若沒(méi)有加載成功,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類(lèi);若都沒(méi)有加載到這個(gè)類(lèi),則跳出ClassNotFoundException異常;否則將這個(gè)找到的類(lèi)生成一個(gè)類(lèi)的定義,并將它加載到內(nèi)存中,最后返回這個(gè)類(lèi)在內(nèi)存中的實(shí)例對(duì)象;

為什么使用雙親委托模型

因?yàn)檫@樣可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類(lèi)的時(shí)候,就沒(méi)有必要子ClassLoader再加載一次;考慮到安全因素,若不使用這種委托模式,那我們就可以隨時(shí)使用自定義的String來(lái)動(dòng)態(tài)替代java核心api中定義的類(lèi)型,這樣會(huì)存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類(lèi)加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠(yuǎn)也無(wú)法加載一個(gè)自己寫(xiě)的String,除非改變JDK中ClassLoader搜索類(lèi)的默認(rèn)算法。

JVM搜索類(lèi)的時(shí)候,如何判定兩個(gè)class是相同的

JVM在判定兩個(gè)class是否相同時(shí),不僅要判斷兩個(gè)類(lèi)名是否相同,而且要判斷是否由同一個(gè)類(lèi)加載器實(shí)例加載的。只有兩者同時(shí)滿足的情況下,JVM才認(rèn)為這兩個(gè)class是相同的

自定義ClassLoader

三個(gè)重要方法

  • loadClassclassloader加載類(lèi)的入口,此方法負(fù)責(zé)加載指定名字的類(lèi),ClassLoader的實(shí)現(xiàn)方法為先從已經(jīng)加載的類(lèi)中尋找,如沒(méi)有則繼續(xù)從父ClassLoader中尋找,如仍然沒(méi)找到,則從BootstrapClassLoader中尋找,最后再調(diào)用findClass方法來(lái)尋找,如要改變類(lèi)的加載順序,則可覆蓋此方法,如加載順序相同,則可通過(guò)覆蓋findClass來(lái)做特殊的處理,例如解密、固定路徑尋找等,當(dāng)通過(guò)整個(gè)尋找類(lèi)的過(guò)程仍然未獲取到Class對(duì)象時(shí),則拋出ClassNotFoundException。如類(lèi)需要resolve,則調(diào)用resolveClass進(jìn)行鏈接。

  • findClass此方法直接拋出ClassNotFoundException,因此需要通過(guò)覆蓋loadClass或此方法來(lái)以自定義的方式加載相應(yīng)的類(lèi)。

  • defineClass此方法負(fù)責(zé)將二進(jìn)制的字節(jié)碼轉(zhuǎn)換為Class對(duì)象,這個(gè)方法對(duì)于自定義加載類(lèi)而言非常重要,如二進(jìn)制的字節(jié)碼的格式不符合JVM Class文件的格式,拋出ClassFormatError;如需要生成的類(lèi)名和二進(jìn)制字節(jié)碼中的不同,則拋出NoClassDefFoundError;如需要加載的class是受保護(hù)的、采用不同簽名的或類(lèi)名是以java.開(kāi)頭的,則拋出SecurityException;如需加載的class在此ClassLoader中已加載,則拋出LinkageError。

在自定義ClassLoader時(shí),一般只重寫(xiě)findClass而不是loadClass

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

JDK已經(jīng)在loadClass方法中幫我們實(shí)現(xiàn)了ClassLoader搜索類(lèi)的判斷方法,當(dāng)在loadClass方法中搜索不到類(lèi)時(shí),loadClass方法就會(huì)調(diào)用findClass方法來(lái)搜索類(lèi),所以我們只需重寫(xiě)該方法即可

自定義ClassLoader

來(lái)自:http://blog.csdn.net/tonytfjing/article/details/47212291

1.定義一個(gè)Person接口

public interface Person {
    public void say();
}

2.再定一個(gè)類(lèi)實(shí)現(xiàn)這個(gè)接口

public class HighRichHandsome implements Person {

    @Override
    public void say() {
        System.out.println("I don't care whether you are rich or not");
    }

}

3.編寫(xiě)ClassLoader

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
    /* 
     * 覆蓋了父類(lèi)的findClass,實(shí)現(xiàn)自定義的classloader
     */
    @Override
    public Class<?> findClass(String name) {
        byte[] bt = loadClassData(name);
        return defineClass(name, bt, 0, bt.length);
    }

    private byte[] loadClassData(String className) {
        InputStream is = getClass().getClassLoader().getResourceAsStream(
                className.replace(".", "/") + ".class");
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        int len = 0;
        try {
            while ((len = is.read()) != -1) {
                byteSt.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteSt.toByteArray();
    }
}

4.測(cè)試類(lèi)

package classloader;

public class LoaderTest {
    public static void main(String args[]) throws Exception{
        test2();
    }
    
    
    private static void test2() throws Exception{
        MyClassLoader loader = new MyClassLoader();
        Class<?> c = loader.loadClass("classloader.HighRichHandsome");
        System.out.println("Loaded by :" + c.getClassLoader());

        Person p = (Person) c.newInstance();
        p.say();

        HighRichHandsome man = (HighRichHandsome) c.newInstance();
        man.say();    
    }

    private static void test3() throws Exception{
    MyClassLoader loader = new MyClassLoader();
    Class<?> c = loader.findClass("com.alibaba.classload.HighRichHandsome");
    System.out.println("Loaded by :" + c.getClassLoader());

    Person p = (Person) c.newInstance();
    p.say();

    //注釋下面兩行則不報(bào)錯(cuò)
    HighRichHandsome man = (HighRichHandsome) c.newInstance();
    man.say();    
}
}

測(cè)試結(jié)果:

Loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
I don't care whether you are rich or not
I don't care whether you are rich or not

遇到的一些問(wèn)題

學(xué)習(xí)ClassLoader的教程是在網(wǎng)上看的;于是在使用sun.misc.Launcher的時(shí)候發(fā)現(xiàn)編譯器找不到這個(gè)類(lèi);但是sun.misc這個(gè)包是存在于rt.jar的,只是apidoc中沒(méi)有,那是因?yàn)閟un開(kāi)頭的包不屬于J2SE規(guī)范,是Sun公司的內(nèi)部實(shí)現(xiàn);
故,使用一下方法解決:

右鍵項(xiàng)目-》屬性-》java bulid path-》jre System Library-》access rules-》resolution選擇accessible,下面填上** 點(diǎn)擊確定


參考資料:http://blog.csdn.net/xyang81/article/details/7292380
http://blog.csdn.net/tonytfjing/article/details/47212291

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

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

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