ClassLoader有幾種
根加載器
你的java程序想要運行,就必須有一個運行環境,這個運行環境既包括底層的jvm支持,還包括基礎的jre類庫支持。那麼這些基礎的jre類本省也是java的class,所以這些class的加載就是由native代碼實現的bootstrap class loader來加載的。
根加載器主要加載的是%JAVA_HOME%/jre/lib/.jar,%JAVA_HOME%/jre/lib/classes/,以及環境變量sun.boot.class.path對應目錄下的類文件。
ext加載器
ExtClassLoader,用於加載%JAVA_HOME%/jre/lib/ext/.jar,%JAVA_HOME%/jre/lib/ext/classes/,以及環境變量java.ext.dirs對應目錄下的類文件。
app加載器
AppClassLoader,主要用於加載用戶目錄下的類文件,以及環境變量java.class.path對應目錄下的類文件。該加載器也是java程序默認的加載器。
自定義的加載器
應用程序是可以通過繼承ClassLoader類實現自己的加載器,來自定義類的加載路徑、處理策略等。我們經常使用的組件和框架大多都有自己自定義的類加載器,例如:tomcat、spring等。
父加載器(上層加載器)
父加載器的作用,是類加載的委派機制所需。自定義的加載器在實例化時可以指定自己的父加載器。
要明確以下兩點
- 父加載器 ≠ 父類。ClassLoader是一個抽象類,內部有一個私有的ClassLoader類型的變量parent。這個就指的是當前類加載器的父加載器。(在本文後面的流程圖中稱作:上層加載器)
- 父加載器 ≠ 加載自己的加載器。假設有兩個自定義的加載器A和B,A可以指定自己的父加載器爲B,但是加載A類的加載器很可能是AppClassLoader,未必是B(但是你可以這樣做)。
要理解以上這兩點,我們可以看下下面這個小程序。
public class MyClassLoaderA extends ClassLoader {
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("1.MyClassLoaderA 's classloader is:\n\t" + MyClassLoaderA.class.getClassLoader());
System.out.println("2.MyClassLoaderA 's parent is:\n\t" + new MyClassLoaderA().getParent());
System.out.println("3.MyClassLoaderA 's classloader 's parent is:\n\t" + MyClassLoaderA.class.getClassLoader().getParent());
System.out.println("4.MyClassLoaderA 's classloader 's grand parent is:\n\t" + MyClassLoaderA.class.getClassLoader().getParent().getParent());
System.out.println("5.AppClassLoader 's classloader is:\n\t" + Class.forName("sun.misc.Launcher$AppClassLoader").getClassLoader());
System.out.println("6.ExtClassLoader 's classloader is:\n\t" + Class.forName("sun.misc.Launcher$ExtClassLoader").getClassLoader());
}
}
最終的輸出如下:
1.MyClassLoaderA 's classloader is:
sun.misc.Launcher$AppClassLoader@18b4aac2
2.MyClassLoaderA 's parent is:
sun.misc.Launcher$AppClassLoader@18b4aac2
3.MyClassLoaderA 's classloader 's parent is:
sun.misc.Launcher$ExtClassLoader@74a14482
4.MyClassLoaderA 's classloader 's grand parent is:
null
5.AppClassLoader 's superclass is:
class java.net.URLClassLoader
6.AppClassLoader 's classloader is:
null
7.ExtClassLoader 's classloader is:
null
以AppClassLoader爲例:
- 他的父類是URLClassLoader
- 他的父加載器是ExtClassLoader
- 他也不是被ExtClassLoader加載的,他和ExtClassLoader都是被根加載器加載的。
ClassLoader的類加載流程
相信你一定聽說過雙親委派模型,雙親委派這個概念其實蠻抽象的。我覺得不必太糾結這個概念的含義,重點是理解jdk內部實現流程即可。
- 最上層的代表根加載器,由於根加載器是本地代碼實現的,jdk裏觀察不到源碼,所以其內部處理流程是大概猜想應該是這樣子。
- CustomClassLoader是假設有這麼一個自定義的類加載器,如果通過他加載一個類,那麼整個流程就應該是如圖所示。
- 你會發現CustomClassLoader、AppClassLoader、ExtClassLoader的內部處理流程是完全一致的。這是因爲jdk的ClassLoader內部實現上採用了模板方法模式,所有類加載器ClassLoader類的子類,都是通過loadClass方法來加載類,而loadClass的方法實現流程已經在ClassLoader內部固化成了模板代碼,每個加載器只需要自己實現findClass方法即可。findClass方法要乾的事情,就對應我們圖中的《自己去找》節點。也就是說,每個繼承自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;
}
}