死磕Tomcat系列(4)——Tomcat中的類加載器

死磕Tomcat系列(4)——Tomcat中的類加載器

在學習Tomcat中的類加載器,而且Tomcat爲何要實現本身的類加載器打破雙親委派模型緣由以前,咱們首先須要知道Java中定義的類加載器是什麼,雙親委派模型是什麼。html

Java中的類加載器

類加載器負責在程序運行時將java文件動態加載到JVM中java

從Java虛擬機的角度來說的話,存在兩種不一樣的類加載器:web

  • 啓動類加載器(Bootstrap ClassLoader):這個類加載器是使用C++語言實現的,是虛擬機自身的一部分。apache

  • 其餘的類加載器:這些類加載器都由Java語言實現,獨立於虛擬機外部,而且全都繼承自抽象類java.lang.ClassLoader,其中其餘類加載器大概又分爲bootstrap

    • ExtensionClassLoader:這個類加載器由ExtClassLoader實現,它負責加載JAVA_HOME/lib/ext目錄中的全部類,或者被java.ext.dir系統變量所指定的路徑中全部的類。
    • ApplicationClassLoader:這個類加載器是由AppClassLoader實現的,它負責加載用戶類路徑(ClassPath)上所指定的全部類,若是應用中沒有自定義本身的類加載器,那麼通常狀況就是程序中默認的類加載器。
    • 自定義加載器:根據本身需求,自定義加載特定路徑的加載器。

對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性緩存

雙親委派模型

上圖中展現的層次結構,稱之爲類加載器的雙親委派模型。雙親委派模型要求除了頂層的啓動類加載器外,其餘加載器都應該有本身的父加載器。這裏的父子關係不是經過繼承來實現的,而是經過設置parent變量來實現的。tomcat

雙親委派模型工做過程是:若是收到一個類加載的請求,自己不會先加載此類,而是會先將此請求委派給父類加載器去完成,每一個層次都是如此,直到啓動類加載器中,只有父類都沒有加載此文件,那麼子類纔會嘗試本身去加載。架構

爲何要設置雙親委派模型呢?實際上是爲了保證Java程序的穩定運行,例如Object類,它是存放在rt.jar中,不管哪個類加載器要加載Object類,最終都會委託給頂層的BootStrapClassLoader,因此全部的類中使用的Object都是同一個類,相反若是沒有雙親委派模型的話,那麼隨意一個類加載器均可以定義一個新的Object類,那麼應用程序將會變得很是混亂。其實雙親委派模型代碼很是簡單。實如今ClassLoader中的loadClass方法下。app

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先,檢查請求類是否被加載過
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
            // 若是沒被本類類加載器加載過,先委託給父類進行加載
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                // 若是沒有父類,則代表在頂層,就交給BootStrap類加載器加載
                    c = findBootstrapClassOrNull(name);
                }
                // 若是最頂層的類也找不到,那麼就會拋出ClassNotFoundException異常
            } catch (ClassNotFoundException e) {

            }
            // 若是父類都沒有加載過此類,子類纔開始加載此類
            if (c == null) {
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

咱們能夠看到findClass方法是須要子類本身去實現的邏輯。框架

Tomcat中的類加載器

下面的簡圖是Tomcat9版本的官方文檔給出的Tomcat的類加載器的圖。

Bootstrap
          |
       System
          |
       Common
       /     \
  Webapp1   Webapp2 ..
  • Bootstrap : 是Java的最高的加載器,用C語言實現,主要用來加載JVM啓動時所須要的核心類,例如$JAVA_HOME/jre/lib/ext路徑下的類。
  • System: 會加載CLASSPATH系統變量所定義路徑的全部的類。
  • Common:會加載Tomcat路徑下的lib文件下的全部類。
  • Webapp一、Webapp2……: 會加載webapp路徑下項目中的全部的類。一個項目對應一個WebappClassLoader,這樣就實現了應用之間類的隔離了。

這3個部分,在上面的Java雙親委派模型圖中都有體現。不過能夠看到ExtClassLoader沒有畫出來,能夠理解爲是跟bootstrap合併了,都是去JAVA_HOME/jre/lib下面加載類。 那麼Tomcat爲何要自定義類加載器呢?

  • 隔離不一樣應用:部署在同一個Tomcat中的不一樣應用A和B,例如A用了Spring2.5。B用了Spring3.5,那麼這兩個應用若是使用的是同一個類加載器,那麼Web應用就會由於jar包覆蓋而沒法啓動。
  • 靈活性:Web應用之間的類加載器相互獨立,那麼就能夠根據修改不一樣的文件重建不一樣的類加載器替換原來的。從而不影響其餘應用。
  • 性能:若是在一個Tomcat部署多個應用,多個應用中都有相同的類庫依賴。那麼能夠把這相同的類庫讓Common類加載器進行加載。

Tomcat自定義了WebAppClassLoader類加載器。打破了雙親委派的機制,即若是收到類加載的請求,會嘗試本身去加載,若是找不到再交給父加載器去加載,目的就是爲了優先加載Web應用本身定義的類。咱們知道ClassLoader默認的loadClass方法是以雙親委派的模型進行加載類的,那麼Tomcat既然要打破這個規則,就要重寫loadClass方法,咱們能夠看WebAppClassLoader類中重寫的loadClass方法。

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        // 1. 從本地緩存中查找是否加載過此類
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        // 2. 從AppClassLoader中查找是否加載過此類
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        String resourceName = binaryNameToPath(name, false);
        // 3. 嘗試用ExtClassLoader 類加載器加載類,防止Web應用覆蓋JRE的核心類
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {
            URL url;
            if (securityManager != null) {
                PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                url = AccessController.doPrivileged(dp);
            } else {
                url = javaseLoader.getResource(resourceName);
            }
            tryLoadingFromJavaseLoader = (url != null);
        } catch (Throwable t) {
            tryLoadingFromJavaseLoader = true;
        }

        boolean delegateLoad = delegate || filter(name, true);

        // 4. 判斷是否設置了delegate屬性,若是設置爲true那麼就按照雙親委派機制加載類
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // 5. 默認是設置delegate是false的,那麼就會先用WebAppClassLoader進行加載
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 6. 若是此時在WebAppClassLoader沒找到類,那麼就委託給AppClassLoader去加載
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }
    throw new ClassNotFoundException(name);
}

最後借用Tomcat官網上的話總結:

Web應用默認的類加載順序是(打破了雙親委派規則):

  1. 先從JVM的BootStrapClassLoader中加載。
  2. 加載Web應用下/WEB-INF/classes中的類。
  3. 加載Web應用下/WEB-INF/lib/*.jap中的jar包中的類。
  4. 加載上面定義的System路徑下面的類。
  5. 加載上面定義的Common路徑下面的類。

若是在配置文件中配置了<Loader delegate="true"/>,那麼就是遵循雙親委派規則,加載順序以下:

  1. 先從JVM的BootStrapClassLoader中加載。
  2. 加載上面定義的System路徑下面的類。
  3. 加載上面定義的Common路徑下面的類。
  4. 加載Web應用下/WEB-INF/classes中的類。
  5. 加載Web應用下/WEB-INF/lib/*.jap中的jar包中的類。

往期文章

如何斷點調試Tomcat源碼

死磕Tomcat系列(1)——總體架構

死磕Tomcat系列(2)——EndPoint源碼解析

死磕Tomcat系列(3)——Tomcat如何作到一鍵式啓停的

一次奇怪的StackOverflowError問題查找之旅

徒手擼一個簡單的RPC框架

徒手擼一個簡單的RPC框架(2)——項目改造

參考文章

相關文章
相關標籤/搜索