若是一個類加載器收到了加載某個類的請求,則該類加載器並不會去加載該類,而是把這個請求委派給父類加載器,每個層次的類加載器都是如此,所以全部的類加載請求最終都會傳送到頂端的啓動類加載器;只有當父類加載器在其搜索範圍內沒法找到所需的類,並將該結果反饋給子類加載器,子類加載器會嘗試去本身加載。java
這樣作的好處就是:Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。例如類java.lang.Object,它存放在rt.jar中,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,所以Object類在程序的各類類加載器環境中都是同一個類。相反,若是沒有使用雙親委派模型,由各個類加載器自行去加載的話,若是用戶本身編寫了一個稱爲java.lang.object的類,並放在程序的ClassPath中,那系統中將會出現多個不一樣的Object類,Java類型體系中最基礎的行爲也就沒法保證,應用程序也將會變得一片混亂。web
其次是考慮到安全因素。假設經過網絡傳遞一個名爲java.lang.Integer的類,經過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會從新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣即可以防止核心API庫被隨意篡改。sql
在JDBC 4.0以後實際上咱們不須要再調用Class.forName來加載驅動程序了,咱們只須要把驅動的jar包放到工程的類加載路徑裏,那麼驅動就會被自動加載。數據庫
這個自動加載採用的技術叫作SPI,數據庫驅動廠商也都作了更新。能夠看一下jar包裏面的META-INF/services目錄,裏面有一個java.sql.Driver的文件,文件裏面包含了驅動的全路徑名。tomcat
使用上,咱們只須要經過下面一句就能夠建立數據庫的鏈接:安全
Connection con = DriverManager.getConnection(url , username , password ) ;
由於類加載器受到加載範圍的限制,在某些狀況下父類加載器沒法加載到須要的文件,這時候就須要委託子類加載器去加載class文件。網絡
JDBC的Driver接口定義在JDK中,其實現由各個數據庫的服務商來提供,好比MySQL驅動包。DriverManager 類中要加載各個實現了Driver接口的類,而後進行管理,可是DriverManager位於 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap類加載器加載,而其Driver接口的實現類是位於服務商提供的 Jar 包,根據類加載機制,當被裝載的類引用了另一個類的時候,虛擬機就會使用裝載第一個類的類裝載器裝載被引用的類。也就是說BootStrap類加載器還要去加載jar包中的Driver接口的實現類。咱們知道,BootStrap類加載器默認只負責加載 $JAVA_HOME中jre/lib/rt.jar 裏全部的class,因此須要由子類加載器去加載Driver實現,這就破壞了雙親委派模型。app
查看DriverManager類的源碼,看到在使用DriverManager的時候會觸發其靜態代碼塊,調用 loadInitialDrivers() 方法,並調用ServiceLoader.load(Driver.class) 加載全部在META-INF/services/java.sql.Driver 文件裏邊的類到JVM內存,完成驅動的自動加載。webapp
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); } private static void loadInitialDrivers() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); }
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); }
這個子類加載器是經過 Thread.currentThread().getContextClassLoader() 獲得的線程上下文加載器。jvm
那麼這個上下文類加載器又是什麼呢?
public Launcher() { ... try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); ... }
能夠看到,在 sun.misc.Launcher 初始化的時候,會獲取AppClassLoader,而後將其設置爲上下文類加載器,因此線程上下文類加載器默認狀況下就是系統加載器。
Tomcat類加載器:
Tomcat如何破壞雙親委派模型的呢?
每一個Tomcat的webappClassLoader加載本身的目錄下的class文件,不會傳遞給父類加載器。
事實上,tomcat之因此造了一堆本身的classloader,大體是出於下面三類目的:
webapp
中的 class
和 lib
,須要相互隔離,不能出現一個應用中加載的類庫會影響另外一個應用的狀況,而對於許多應用,須要有共享的lib以便不浪費資源。jvm
同樣的安全性問題。使用單獨的 classloader
去裝載 tomcat
自身的類庫,以避免其餘惡意或無心的破壞;tomcat
修改文件不用重啓就自動從新裝載類庫而驚歎吧。