Java類加載器

Java中由類字節碼流轉化爲JVM運行時類數據必須使用類加載器進行加載,Java中提供了三個類加載器:根類加載器,擴展類加載器,應用程序類加載器,使用的機制可以概括爲“全盤負責雙親委託”機制。

這裏寫圖片描述

注意圖中的關係是委派關係,不是繼承關係!源碼中使用組合實現,即ClassLoader類的parent成員變量,而最上面的那個類加載器(根類加載器)的parent爲null。



雙親委託機制在代碼中具體體現在ClassLoader類的loadClass方法(下面是JDK1.8代碼):

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();
                    //由類全限定名得到Class對象,自定義類加載器(僅)需要重寫該方法
                    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;
        }
    }


自定義類加載器


public class MyClassLoader extends ClassLoader {

    public MyClassLoader() {    
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //這裏類字節流來源爲class文件
        String path = "C:\\Users\\mao\\Desktop\\" + name + ".class";
        //讀取class文件,轉化爲字節流
        byte[] b = IoUtils.getBytes(path);
        return defineClass(name, b, 0, b == null ? 0 : b.length);
    }
}

這樣就定義了一個自己的ClassLoader,接下來爲測試代碼,在C:\Users\mao\Desktop\路徑下放一個String.class文件,然後使用該ClassLoader加載的一個class文件:

public class Test {

    public static void main(String[] args) throws Exception {
        //沒有指定父類加載器,則會被默認設置爲應用程序類加載器爲父類加載器
        ClassLoader cl = new MyClassLoader();
        Class<?> clazz = Class.forName("String", true, cl);
        Object obj = clazz.newInstance();
        System.out.println(obj);
    }
}

代碼打印:

I am a custom String class

因爲我們放在C:\Users\mao\Desktop\路徑下的String類的toString方法被重寫了:

public class String {

    @Override
    public java.lang.String toString() {
        return "I am a custom String class";
    }
}

注意:這個String類是我們自己定義的,而JDK內部也有一個String類,運行時這兩個類當然不是等價的,因此至少類加載器不同(當然上面的例子類全限定名也不同),而我們是無法做到把JDK的String類替換成我們自己定義的String類的(像上面的例子根類加載器會拒絕加載),這也說明了雙親委派機制的一個優點就是安全性高。



另外,Thread類有一個成員變量contextClassLoader,表示線程上下文類加載器,可以通過setContextClassLoader和getContextClassLoader方法設置和獲取該變量的值,默認情況下contextClassLoader的值爲父線程的contextClassLoader的值,而Java中最頂層的線程的contextClassLoader爲應用程序類加載器。這個是在JDK1.2開始引入的,爲了解決“頂層類”需要調用“底層類”卻識別不了的問題。

本博客已停止更新,轉移到微信公衆號上寫文章,歡迎關注:Android進階驛站
Android進階驛站

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章