前言:網上關於類加載器講解的文章特別多,在學習的時候讓我受益匪淺,某段時間覺得自己懂了。但是在昨天遇到一個問題,並去看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)加載器加載,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回,只有當父類加載器無法完成此加載任務時,才自己去加載
雙親委派機制下三種類加載器的關係(圖片來自網絡):
ClassLoaderA和ClassLoaderB是我們自己實現的類加載器,這裏都指定了其父加載器爲APPClassLoader,當然你也可以指定ClassLoaderA的父加載器爲ClassLoaderB,但由於往上最多隻能拿到SystemClassLoader的引用,所以父加載器最多隻能指定到SystemClassLoader
通過 java.lang.ClassLoader.getSystemClassLoader
我們可以獲取到JVM啓動時加載ClassPath裏jar包的應用類加載器SystemClassLoader,由此我們可以從代碼上看到上面的關係圖:
這裏可以看到擴展類加載器的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
方法調用了native
的findBootstrapClass
方法,所以是映射到了Bootstrap類加載器的
再來看看擴展類加載器和應用類加載器的類圖
可以發現它們都繼承自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