雙親委派機制源碼分析以及自定義類加載器

雙親委派

雙親委派:如果一個類加載器收到了加載某個類的請求,則該類加載器並不會去加載該類,而是把這個請求委派給”父類加載器“,每一個層次的類加載器都是如此,因此所有的類加載請求最終都會傳送到頂端的啓動類加載器;只有當”父類加載器“在其搜索範圍內無法找到所需的類,並將該結果反饋給子類加載器,子類加載器會嘗試去自己加載。

jvm中加載器層次關係:

啓動類加載器BootStrapClassLoader --------> 擴展類加載器ExtensionClassLoader -------->
應用加載器AppClassLoader --------> 自定義加載器

  • BootStrapClassLoader:

    加載/lib/下的jar(包括Java核心類庫rt.jar等其他),因爲是C++寫的,程序裏面打印出來是null
    官方註釋:Some implementations may use null to represent the bootstrap class loader

  • ExtensionClassLoader:

    加載/lib/ext/下的jar,Java實現,可以在程序裏裏面獲取

  • AppClassLoader:

    又叫SystemClassLoader,classpath下面的即我們寫的代碼,以及第三方引入的依賴,都是他加載的

示例代碼:

package day20200228;


import com.sun.nio.zipfs.ZipFileSystem;
import junit.extensions.TestDecorator;
import sun.net.spi.nameservice.dns.DNSNameService;

import javax.crypto.Cipher;

/**
 * @Author: xiaoshijiu
 * @Date: 2020/2/29
 * @Description: $value$
 */
public class TestClassLoad {

    public static void main(String[] args) {

        /**
         * BootStrapClassLoad
         */
        System.out.println(Object.class.getClassLoader());  // rt.jar
        System.out.println(String.class.getClassLoader());  // rt.jar
        System.out.println(Math.class.getClassLoader());    // rt.jar
        System.out.println(Cipher.class.getClassLoader());  // jce.jar

        System.out.println("================");

        /**
         * ExtensionClassLoad
         */
        System.out.println(ZipFileSystem.class.getClassLoader());  // ext/zipfs.jar
        System.out.println(DNSNameService.class.getClassLoader());  // ext/dnsns.jar

        System.out.println("================");

        /**
         * AppClassLoad/SystemClassLoad   
         */
        System.out.println(TestClassLoad.class.getClassLoader());   // 自己寫的
        System.out.println(ClassLoadingProcessStatic.class.getClassLoader());   // 自己寫的
        System.out.println(TestDecorator.class.getClassLoader());   // 引入的junit依賴中的
    }

}

運行結果
在這裏插入圖片描述

雙親委派的源碼實現

java.lang.ClassLoader中的,通過loadClass方法加載類,也就是雙親委派的具體實現

// name參數,即binary name,類的全類名
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,檢查這個類是不是被加載過了;findLoadedClass方法檢查
            Class<?> c = findLoadedClass(name);
            // 沒有被加載過
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    // ”父類“不爲空,找父類加載器加載;loadClass方法:仍然調用該方法,即產生遞歸;
                        c = parent.loadClass(name, false);
                    } else {
                    // ”父類“爲空,即代表”父類“爲BootstrapClassLoader,找其加載
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //異常
                }

                if (c == null) {        
                    long t1 = System.nanoTime();
                    // 如果還沒有加載到,c仍然爲空,那麼調用該加載器的findClass方法,去加載
                    c = findClass(name);

                    // 記錄時間
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

上面的一步一步走下來,註釋寫得都很清楚,流程也是很清楚了;

再看看基類java.lang.ClassLoader中的findClass方法
在這裏插入圖片描述
空方法,沒有實現,所以要想自定義類加載器,必須要繼承基類並重寫該findClass方法

`findClass`方法將 binary name 變成 Class 對象
這裏有一個關於binary name的解釋:
$ 表示裏面的內部類,$ 數字裏面的第幾個匿名內部類

在這裏插入圖片描述

自定義類加載器

前面說了要想自定義類加載器,必須要繼承基類並重寫該findClass方法

findClass方法 將 binary name 變成 Class 對象
另外在結合: defineClass方法 可以將class文件內容的字節數組轉成Class對象
defineClass方法 在基類`java.lang.ClassLoader`中有實現

這樣一來,我們只需要 根據class文件名 binary name 獲得 class文件內容的byte[]形式 
再根據defineClass方法,將byte[]數據變爲Class對象,完成。
package day20200229;


import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @Author: xiaoshijiu
 * @Date: 2020/2/29
 * @Description: 自定義 ClassLoader
 * 繼承 基類ClassLoader,重寫 findClass方法
 * findClass方法:將 binary name 變成 Class對象
 * 其中還需要另外一個方法 defineClass :可以將class文件的字節數組轉成Class對象
 */
public class MyClassLoader extends ClassLoader {


    static String path;

    public MyClassLoader() {
        super();
    }

    // 指定 上級類加載器的構造方法
    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    /**
     * @param name binary name
     */
    @Override
    protected Class<?> findClass(String name) {
        byte[] bytes = classToBytes(name);
        return defineClass(name, bytes, 0, bytes.length);
    }

    /**
     * 抽取出來一個私有方法,將class文件內容變成bytes數組
     *
     * @param name binary name
     */
    private byte[] classToBytes(String name) {
        FileInputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        try {
            // 判空
            if (name == null || "".equals(name)) {
                return null;
            }
            // 加載指定path路徑的class文件
            if (path == null) {
            	// 文件位置,.替換成/
                name = name.replace(".", "/") + ".class";
            } else {
                name = path + name.replace(".", "/") + ".class";
            } 

            inputStream = new FileInputStream(name);
            outputStream = new ByteArrayOutputStream();
            // 一次接受50字節
            byte[] bytes = new byte[50];
            int len;
            while ((len = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
            }
            return outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return null;
    }


 	 // 測試代碼
	 public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader();

        // classpath下的類(自己寫的類),雙親委派機制,由AppClassLoader加載
        Class<?> c = classLoader.loadClass("day20200222.TestVolatile");
        System.out.println(c.getClassLoader());

        // 硬盤中的類(g:/day20190820.ListNodeSum.class),雙親都加載不了,由該自定義的加載器加載
        path = "g:/";
        Class<?> c2 = classLoader.loadClass("day20190820.ListNodeSum");
        System.out.println(c2.getClassLoader());
    }
}

上面兩組測試代碼:

  • 第一,day20200222.TestVolatile該類位於當前項目工程中,即classpath下,根據雙親委派機制,由AppClassLoader加載
  • 第二,day20190820.ListNodeSum位於g盤中,根據雙親委派機制,”父類加載器“均不能加載,最後反饋到該類加載器,其findClass方法可以正確加載,即自定義加載器完成加載

運行結果
在這裏插入圖片描述

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