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

前言

雙親委派模型是Java加載類的機制.採用雙親委派模型的好處是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文件的包名,在後續代碼中須要使用類的全限定名稱.app

//
// 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方法:ide

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 內存區域及內存溢出分析post

JVM的判斷對象是否已死和四種垃圾回收算法性能

JVM 配置經常使用參數和經常使用 GC 調優策略測試

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

最後

後續會持續更新性能優化專題知識,寫的很差的地方也但願大牛能指點一下,你們以爲不錯能夠點個贊在關注下,之後還會分享更多文章!

相關文章
相關標籤/搜索