JVM - 徹底理解打破雙親委派機制

在這裏插入圖片描述

Pre 雙親委派

JVM-白話聊一聊JVM類加載和雙親委派機制源碼解析

JVM - 自定義類加載器


何爲打破雙親委派

舉個例子 有個類 Artisan

我們希望通過自定義加載器 直接從某個路徑下讀取Artisan.class . 而不是說 通過自定義加載器 委託給 AppClassLoader ------> ExtClassLoader ----> BootClassLoader 這麼走一遍,都沒有的話,才讓自定義加載器去加載 Artisan.class . 這麼一來 還是 雙親委派。

我們期望的是 Artisan.class 及時在 AppClassLoader 中存在,也不要從AppClassLoader 去加載。

說白了,就是 直接讓自定義加載器去直接加載Artisan.class 而不讓它取委託父加載器去加載,不要去走雙親委派那一套。

我們知道 雙親委派的機制是在ClassLoader # loadClass方法中實現的,打破雙親委派,那我們是不是可以考慮從這個地方下手呢?


如何打破雙親委派

核心: 重寫ClassLoader#loadClass方法


演示

剛纔的思路是對的,要打破它,那就搞loadClass方法。

重寫loadClass方法唄。

我們基於 JVM - 自定義類加載器 再來搞一搞

需要再此基礎上 重寫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) {
                     
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    
                    //調用URLClassLoader的findClass方法在加載器的類路徑裏查找並加載該類
                    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 不就好了?

我們把loadClass方法的源碼copy過來 把雙親委派的部分代碼去掉吧,走 改下

重寫 ClassLoader#loadClass


public class MyClassLoaderTest {

    static class MyClassLoader extends ClassLoader {
        private String classPath;

        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }

        private byte[] loadByte(String name) throws Exception {
            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;
        }



        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) {
                    c = findClass(name);
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                //defineClass將一個字節數組轉爲Class對象,這個字節數組是class文件讀取後最終的字節數組。
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    }

重點

   protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 嘗試加載,不存在直接去findClass ,不走委託父類
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    c = findClass(name);
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

運行下

在這裏插入圖片描述


失敗原因探究

略微尷尬, Object.class 找不到 。 爲啥 呢? 你加載Boss1的時候, Boss1的父類也需要被加載, 你又把雙親委派給關了, 這個自定義的加載器在本地路徑下是找不到Object.class的 。

咋辦? 放到自定義的加載器加載的路徑下 ?

-----> 其實是不行的,Object 誰能篡改的了啊 ,Object只能由引導類加載器來加載。

臨時解決辦法

所以換個思路 ,自己的類路徑下的對象走我自己的classLoader, 其他的類 還是走雙親委派

 if ("com.gof.facadePattern.Boss1".equals(name)){
      c = findClass(name);
 }else{
     // 交由父加載器去加載
      c = this.getParent().loadClass(name);
 }

驗證是否成功

這個時候我們在AppClassLoader加載的路徑下 再創建個Boss1 (如果走的還是雙親委派,那加載器肯定還是AppClassLoader)

看 是不是這個Boss1 還是被自定義的ClassLoader加載,如果是,說明打破成功。

應用下新建Boss1類

自定義加載路徑D:/artisan/com/gof/facadePattern下保留Boss1.class

驗證

**加粗樣式**

輸出結果

在這裏插入圖片描述

OK,雙親委派機制 打破成功。

這個在tomcat類加載機制中非常重要,所以需要徹底明白這一點。

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