JVM面試問題系列:Java類載入機制之雙親委派模型

若小寒發表於2019-03-28

前言

雙親委派模型是Java載入類的機制.採用雙親委派模型的好處是Java類隨著它的類載入器一起具備了一種帶有優先順序的層級關係,通過這種層級關係可以避免類的重複載入.

1. 模型基礎

類載入器模型

  • Bootstrap ClassLoader(啟動類載入器): 負責將%JAVA_HOME%/lib目錄中或-Xbootclasspath中引數指定的路徑中的,並且是虛擬機器識別的(按名稱)類庫載入到JVM中
  • Extension ClassLoader(擴充套件類載入器): 負責載入%JAVA_HOME%/lib/ext中的所有類庫
  • Application ClassLoader(應用程式載入器): 負責ClassPath中的類庫

2. 為什麼使用雙親委派模型?

1.雙親委派模型最大的好處就是讓Java類同其類載入器一起具備了一種帶優先順序的層次關係。這句話可能不好理解,我們舉個例子。比如我們要載入java.lang.Object類,無論我們用哪個類載入器去載入Object類,這個載入請求最終都會委託給Bootstrap ClassLoader,這樣就保證了所有載入器載入的Object類都是同一個類。如果沒有雙親委派模型,那就亂了套了,完全可能搞出多個不同的Object類。
2.自上而下每個類載入器都會盡力載入.

3. 看看原始碼

1.首先載入類呼叫的loadClass方法,我們找到ClassLoader的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;
        }
    }複製程式碼
  • 首先判斷了該類是否已載入.
  • 若沒載入,則傳給雙親載入器去載入,
  • 若雙親載入器沒能成功載入它,則自己用findClass()去載入.所以是個向上遞迴的過程.
  • 自定義載入器時,需要重寫findClass方法,因為是空的,沒有任何內容:
 protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }複製程式碼

4. 自己動手,編寫一個自己的類載入器

1.首先需要一個編譯好的class檔案,筆者用了一個之前寫的斐波那契的類Fib.class(所在路徑:C:/Users/Think/crabapple),下面是用idea通過反編譯方式開啟的class檔案,注意記下class檔案的包名,在後續程式碼中需要使用類的全限定名稱.

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package crabapple;

public class Fib {
  
    public static int fib(int num) {
        return num < 2 ? num : fib(num - 2) + fib(num - 1);
    }
}複製程式碼

2.繼承ClassLoader,重寫findClass方法:

class MyClassLoader extends ClassLoader {
    private String classPath;  // 儲存的地址

    /**
     * 傳入地址建構函式
     * @param classPath
     */
    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    /**
     * 讀取class檔案
     * @param name
     * @return
     * @throws Exception
     */
    private byte[] loadByte(String name) throws Exception {
        String inPath = classPath + "/" + name + ".class";
        FileInputStream fis = new FileInputStream(inPath);
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;
    }

    /**
     * 重寫findClass方法,讓載入的時候呼叫findClass方法
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            // 將位元組碼載入記憶體
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}複製程式碼
  • loadByte方法僅用作讀取檔案
  • findClass方法才是載入類到記憶體的,注意name必須填全限定名,比如java.lang.Object.

3.測試,一下將使用一些反射機制和class類的方法.

public class ClassLoaderTest extends ClassLoader {

    //main函式本該丟擲異常有 ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException,為了好看,簡寫成Exception
    public static void main(String[] args) throws Exception {
        //初始化類載入器
        MyClassLoader myClassLoader=new MyClassLoader("C:/Users/Think/crabapple");
        //載入Fib類,筆者class檔案包名為crabapple
        Class myClass=myClassLoader.loadClass("crabapple.Fib");
        //獲取載入類的例項
        Object object=myClass.newInstance();
        //獲取該類一個名為fib,且引數為int的方法
        Method method=myClass.getMethod("fib",int.class);
        //執行這個方法
        int result=method.invoke(object,4);
        //列印結果
        System.out.print(result);
        
        //output
        /**
         * 3
         * Process finished with exit code 0
         */
    }
}複製程式碼
  • 執行成功
  • 我們來分析下,Fib類的載入過程,初始化自定義類載入器後,loadClass方法肯定將其委派到雙親Application ClassLoader,而Application ClassLoader又將其委派到Extension ClassLoader,繼而委派到Bootstrap ClassLoader.但是Bootstrap ClassLoader發現Fib並不在自己的載入能力範圍內,於是移向Extension ClassLoader,同理Extension ClassLoader只能載入/ext中的class,繼而讓給Application ClassLoader,而Application ClassLoader只載入classpath中的類,於是又回到我們自定義的MyClassLoader,幸好我們重寫了findClass方法進而執行了載入,否在findClass丟擲找不到類的異常.至此Fib類載入完成.

JVM系列:

深入詳解JVM 記憶體區域及記憶體溢位分析

JVM的判斷物件是否已死和四種垃圾回收演算法

JVM 配置常用引數和常用 GC 調優策略

7種JVM垃圾收集器特點,優劣勢、及使用場景!

最後

後續會持續更新效能優化專題知識,寫的不好的地方也希望大牛能指點一下,大家覺得不錯可以點個贊在關注下,以後還會分享更多文章!

在這給大家推薦一個微信公眾號,那裡每天都會有技術乾貨、技術動向、職業生涯、行業熱點、職場趣事等一切有關於程式設計師的內容分享。更有海量Java架構、移動網際網路架構相關原始碼視訊,面試資料,電子書籍截止於4月28日免費發放。我看了覺得資源還不錯,如果你們有需要的話,掃描下方二維碼關注wx公眾號免費獲取↓↓↓

JVM面試問題系列:Java類載入機制之雙親委派模型

資源大本營↓↓↓

Java架構資料
Java原始碼解析,到各種框架學習,再到專案實戰,一應俱全,包括但不限於:Spring、Mybatis等原始碼、Java進階、Java架構師、虛擬機器、效能優化、併發程式設計、資料結構和演算法。

JVM面試問題系列:Java類載入機制之雙親委派模型

JVM面試問題系列:Java類載入機制之雙親委派模型

JVM面試問題系列:Java類載入機制之雙親委派模型


相關文章