ClassLoader和雙親委派機制 原 薦

前言:網上關於類加載器講解的文章特別多,在學習的時候讓我受益匪淺,某段時間覺得自己懂了。但是在昨天遇到一個問題,並去看Spark關於用戶類加載的時候,它實現的類加載器讓我看的很疑惑,半天沒有轉過來。我才發現自己原來根本不懂類加載器的原理,對雙親委派機制只是停留在字面上,知道會先找父但是不知道怎麼去找的,所以今天把JDK關於ClassLoader的代碼擼了一遍,把以前一些模糊的地方捋明白了,內心稍安。同時這也是我昨天遇到的問題的前篇,掃清後面問題的障礙,後續會把關於Spark的問題捋出來,再來分享

三種缺省類加載器

當一個JVM啓動的時候,Java默認有三種類加載器

  • 啓動(Bootstrap)類加載器:Bootstrap類加載器是由C和C++實現的類加載器,它負責將 <Java_Runtime_Home>/lib 或者由 -Xbootclasspath 指定位置下的類庫加載到內存中。由於是native的,所以我們無法直接接觸它,也無法直接引用它。在JDK的ClassLoader類裏可以看到關於它的方法調用都是private native
  • 擴展(Exttension)類加載器:ExtClassLoader是由Sun公司提供的實現,它負責將 < Java_Runtime_Home >/lib/ext 或者由系統變量 java.ext.dir 指定位置中的類庫加載到內存中,在 sun.misc.Launcher$ExtClassLoader 可看到源碼,由於它的訪問權限是default,所以只能是包內可見,我們外部無法直接引用它
  • 應用(Application)類加載器(也稱系統類加載器SystemClassLoader):AppClassLoader也是由Sun公司提供的實現,它負責將系統類路徑(CLASSPATH)中指定的類庫加載到內存中,在 sun.misc.Launcher$AppClassLoader 可看到源碼,它的訪問權限同樣是default,所以只能是包內可見,我們外部無法直接引用它

雙親委派機制

通俗的講,就是某個特定的類加載器在接到加載類的請求時,它首先會查看自己已加載的類中是否有這個類,如果有就返回,如果沒有就將加載任務委託給父類(parent)加載器加載,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回,只有當父類加載器無法完成此加載任務時,才自己去加載

雙親委派機制下三種類加載器的關係(圖片來自網絡):

classloader relation

ClassLoaderA和ClassLoaderB是我們自己實現的類加載器,這裏都指定了其父加載器爲APPClassLoader,當然你也可以指定ClassLoaderA的父加載器爲ClassLoaderB,但由於往上最多隻能拿到SystemClassLoader的引用,所以父加載器最多隻能指定到SystemClassLoader

通過 java.lang.ClassLoader.getSystemClassLoader 我們可以獲取到JVM啓動時加載ClassPath裏jar包的應用類加載器SystemClassLoader,由此我們可以從代碼上看到上面的關係圖:

classloader

這裏可以看到擴展類加載器的parent爲null,並不是Bootstrap類加載器,那雙親委派到這一級是如何實現的呢? 其次應用類加載器的父加載器爲什麼是擴展類加載器呢?

雙親委派機制的實現

關係圖裏父加載器的來源

擴展類加載器和應用類加載器都是由JVM創建的,通過 sun.misc.Launcher 類的構造函數我們可以看到創建過程(具體看註釋),如下:

public Launcher() {
  Launcher.ExtClassLoader var1;
  try {
    // 先創建擴展類加載器,加載<Java_Runtime_Home>/lib/ext下的jar包,擴展類加載器的構造方法默認指定的父類加載器就是null,因爲我們引用不到BootstrapClassLoader
    var1 = Launcher.ExtClassLoader.getExtClassLoader();
  } catch (IOException var10) {
    throw new InternalError("Could not create extension class loader");
  }

  try {
    // 接着創建應用類加載器,並指定其父加載器爲上面創建的ExtClassLoader,由此其父加載器爲ExtClassLoader的關係成立
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
  } catch (IOException var9) {
    throw new InternalError("Could not create application class loader");
  }
  // 並把當前現場的類加載器設置爲應用類加載器
  Thread.currentThread().setContextClassLoader(this.loader);
  
  // ... other code 
}

由於Launcher類裏有自己的靜態實例,所以該類被BootstrapClassLoader加載的時候就執行了上述過程,創建了擴展類加載器和應用類加載器,並且指定了父加載器關係

雙親委派的機制

除啓動類加載器是native實現的外,其他所有類加載器都是繼承自 java.lang.ClassLoader 抽象類,該類有一個protected的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 {
                    // 使用Bootstrap類加載器加載,所以當ExtClassLoader的parent爲null時,它會請求Bootstrap類加載器加載,這樣雙親委派機制就是成立的
                    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;
    }
}

其中 findBootstrapClassOrNull 方法調用了 nativefindBootstrapClass 方法,所以是映射到了Bootstrap類加載器的

再來看看擴展類加載器和應用類加載器的類圖

structure

可以發現它們都繼承自URLClassLoader類,而URLClassLoader類又繼承自SecureClassLoader類,在這兩個父類中都沒有對ClassLoader抽象類的 loadClass 方法重寫,而除了AppClassLoader類對 loadClass 方法做了簡單包裝之外,都沒有去更改ClassLoader抽象類的 loadClass 方法原始邏輯,所以 這幾個類加載器都保留了雙親委派機制,而如果要改變這種機制,我們可以通過重寫這個方法實現

自己實現ClassLoader

實現一個ClassLoader比較簡單,我們像擴展類加載器和應用類加載器一樣繼承 URLClassLoader 就可以實現我們自己的類加載器,並利用它加載包和我們的class

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class MyClassLoaderTest {
  static class MyClassLoader extends URLClassLoader {
    public MyClassLoader(URL[] urls, ClassLoader parent) {
      super(urls, parent);
    }

    // 對外暴露addURL方法
    @Override
    public void addURL(URL url) {
      super.addURL(url);
    }
  }
  
  public static void main(String[] args) throws Exception {
    String path = "D:\\maven\\repository\\commons-codec\\commons-codec\\1.9\\commons-codec-1.9.jar";

    // 也可用 ClassLoader.getSystemClassLoader() 獲取應用類加載器作爲父加載器或者直接置父加載器爲null
    MyClassLoader myClassLoader = new MyClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
    myClassLoader.addURL(new File(path).toURI().toURL());

    // 加載包裏的類
    Class<?> clazz = myClassLoader.loadClass("org.apache.commons.codec.digest.DigestUtils");
    Method md5Hex = clazz.getDeclaredMethod("md5Hex", String.class);
    // 執行MD5
    String result = (String) md5Hex.invoke(null, "my classloader test");
    // 判斷結果
    assert result.equals("53c6c4f7ceadadcf36ce1386678fb61b");
    // 設置當前線程的類加載器爲我自己的類加載器
    // Thread.currentThread().setContextClassLoader(myClassLoader);
  }
}

歡迎閱讀轉載,轉載請註明出處:https://my.oschina.net/kavn/blog/1579576

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