JVM(六):探究類加載過程-下

JVM(六):探究類加載過程-下

上文說了類加載過程的5個階段,着重介紹了各個階段作的工做。在本文中,咱們對執行加載階段的主體進行探討,學習類加載器的模型和邏輯,以及咱們該如何自定義一個類加載器。java

定義

前面說過加載階段是一個可讓設計人員高度自控的模塊,由於類文件的源頭能夠是多種多樣的,代碼生成、反射生成或從網絡中生成等。所以類加載器做爲對這些文件的處理就顯得尤其重要。數組

但類加載器的功能不只如此,其還有一個重要的功能就是和一個類的全限定名惟一肯定一個類。通俗來講,要說兩個類是相同的,不只其全限定名要同樣,其對應的類加載器也必須相同,才能說明兩個類是相等的。網絡

正由於類加載器的功能角色如此重要,所以虛擬機對其的實現規範也十分重視。在Java虛擬機中,對其的實現模型是雙親委派模型jvm

模型

雙親委派模型

雙親委派模型的主要執行過程示意圖如上所示,其分爲啓動類加載器(Bootstrap Class-loader),拓展類加載器(Extension Class-loader),應用程序類加載(Application Class-loader)。ide

其中啓動類加載器主要負責加載 JRE 的核心類庫,如 JRE 目錄下的 rt.jar。但其實根據《深刻分析 Java Web 技術內幕》上所說,啓動類加載器並不嚴格符合雙親委派模型,由於Bootstrap Class-loader 並不屬於 JVM 的類等級層次。Bootstrap Class-loader 是沒有子類的,Extension Class-loader 也是沒有父類的。不過在這裏咱們並不深究,只要知道有這一點就能夠了。源碼分析

Extension Class-loader 主要負責加載 JRE 拓展目錄 ext 下的類。佈局

Application Class-loader 主要負責用戶類路徑(Class-path)下的類,這個類加載器是使用的最多的,由於大大多數狀況下,通常開發者並無實現自定義的類加載器,那麼 JVM 就會使用這個來加載類大部分類。學習

執行過程

雙親委派模型執行過程

上圖就是雙親委派模型的執行過程,當類開始加載的時候,先檢查是否已經被加載過,若是沒有被加載過,則調用父類的加載方法,若是父類加載失敗,拋出異常,則調用自身的 findClass() 方法進行加載。this

JDK 中加載過程的源碼分析:url

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 {
                    // 無父親就調用 bootstarp 加載器來加載
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
        // 父加載器和 bootstarp 加載器都沒有找到指定類,調用當前類的 findClass() 來完成類加載
        // 所以,自定義類加載器,就是重寫 findClass() 方法
        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;
}
}

從源碼中,咱們能夠看到其實符合規範要求的 雙親委派模型 的。而當咱們要自定義一個類加載器的時候就是經過重寫 findClass() 來實現的。

自定義類加載器

/**
 * 1. 自定義類加載器經過集成ClassLoader來實現,主要經過重寫findClass方法
 * 2. findClass方法首先經過自定義的loadByte()方法將Class文件轉換成byte[]字節流
 * 3. 而後經過defineClass()方法將其轉換爲Class對象
 */
public class SelfClassLoader extends ClassLoader {
    private String classPath;
    public SelfClassLoader(String classPath) {
        this.classPath = classPath;
    }

    /**
     * 經過 difineClass,將一個字節數組轉換爲Class 對象
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    /**
     * 根據路徑將指定的文件讀取爲byte 流
     * @param name
     * @return
     * @throws IOException
     */
    private byte[] loadByte(String name) throws IOException {
        name = name.replaceAll("\\.", "/");
        FileInputStream fis = new FileInputStream(classPath + "/" + name
                + ".class");
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }
}

另外一個種實現自定義類加載器的方法:

/**
 * 1. 加載指定packageName下的類
 * 2. 用自定義類加載器進行加載,若是加載失敗,再交給父加載器進行加載
 */
public class UrlSelfClassloader extends URLClassLoader {
    private String packageName = "";

public UrlSelfClassloader(URL[] urls, ClassLoader parent) {
    super(urls, parent);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class<?> aClass = findLoadedClass(name);
    if (Objects.nonNull(aClass)){
        return aClass;
    }
    if (!packageName.startsWith(name)){
        return super.loadClass(name);
    }else {
        return findClass(name);
    }
}
}

如何使用自定義的類加載器

public static void main(String args[]) throws Exception {
    MyClassLoader classLoader = new MyClassLoader("");
    Class clazz = classLoader.loadClass("");
    Object obj = clazz.newInstance();
    Method helloMethod = clazz.getDeclaredMethod("hello", null);
    helloMethod.invoke(obj, null);
}

總結

在本文中,咱們講解了類加載器的實現模型,分析了在 JDK 中類加載器的源碼實現,並根據源碼中的代碼實現,自定義了一個類加載器的實現。

此外相信通過五和六兩篇文章的學習,你們應該對如何將類加載入虛擬機中有了系統的理解。

後面的文章中,咱們就要進入 JVM 的內部了,從下篇文章開始,咱們就開始逐步講解 JVM 的內存佈局,瞭解 JVM 中的各個邏輯上劃分的存儲結構以及其做用,歡迎各位讀者瀏覽。

iceWang公衆號

文章在公衆號「iceWang」第一手更新,有興趣的朋友能夠關注公衆號,第一時間看到筆者分享的各項知識點,謝謝!筆芯!

本系列文章主要借鑑《深刻分析 Java Web 技術內幕》和《深刻理解 Java 虛擬機 - JVM 高級特性與最佳實踐》。

相關文章
相關標籤/搜索