ClassLoader中那些傻傻分不清的概念

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等。

父加載器(上層加載器)

父加載器的作用,是類加載的委派機制所需。自定義的加載器在實例化時可以指定自己的父加載器。
要明確以下兩點

  1. 父加載器 ≠ 父類。ClassLoader是一個抽象類,內部有一個私有的ClassLoader類型的變量parent。這個就指的是當前類加載器的父加載器。(在本文後面的流程圖中稱作:上層加載器)
  2. 父加載器 ≠ 加載自己的加載器。假設有兩個自定義的加載器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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章