須要上級處理的雙親委派模型

大綱

[TOC]java

前言

類加載器是面試的高頻題,類加載器的雙親委派模型更是重中之重,基本問到類加載器就會問到雙親委派模型,那麼他究竟是什麼,又有什麼好處呢git

咱們先來個模擬個這樣的情景:github

咱們打仗的時候,另一個陌生的隊伍讓你調兵支援,你是自做主張,仍是向上級彙報,讓上級處理,請求上級指示,按照指示行動呢?面試

這個場景跟雙親委派的作法有一點類似,你能夠考慮下那種作法比較好,又有什麼好處。spring


個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關,劍指offer題解(Java版),能夠點個star。能夠看個人github主頁,天天都在更新喲。bootstrap

邀請您跟我一同完成 repotomcat


類加載器

上一節咱們知道了類加載的過程,其中加載階段的第一步"經過一個類的全限定名來獲取描述此類的二進制字節流"這個動做放到虛擬機外部實現,以便讓應用程序本身決定如何去獲取所須要的類。這個過程的代碼模塊就是類加載器app

類相等條件

  • 同一個類文件
  • 同一個虛擬機加載
  • 同一個類加載器加載
  • 其中一個不一樣,這兩個類就不相等

類加載器分類

  • 啓動類加載器(Bootstrap ClassLoader)
    • 將放在<JAVA_HOME>\lib目錄中的或者被-Xbootclasspath參數所指定的路徑中的,而且是虛擬機識別的類庫加載到虛擬機內存(注意三個關鍵詞)
    • 啓動類加載器沒法被Java程序直接引用
  • 擴展類加載器(Extension ClassLoader)
    • 負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的全部庫
    • 開發者能夠直接使用這個類加載器
  • 應用程序類加載器(Application ClassLoader)
    • 負責加載用戶類路徑上所指定的類庫
    • 開發者能夠直接使用
    • 若是應用程序中沒有定義過本身的類加載器,通常狀況下默認的類加載器
  • 用戶自定義類加載器

若是劃分的粗一點,那麼只用兩種ide

  • 啓動類加載器
    • 在HotSpot中(其餘虛擬機可能不同),這個是由C++編寫的,是虛擬機的一部分
  • 其餘類加載器
    • 由Java編寫。
    • 獨立於虛擬機外部
    • 所有繼承自java.lang.ClassLoader

雙親委派模型

上面咱們已經介紹了類加載器,大體能夠分爲四種:模塊化

  • 啓動類加載器
  • 擴展類加載器
  • 應用程序類加載器
  • 用戶自定義的類加載器

他們是相互配合工做的,若是類加載之間的層次是下圖這種關係,那麼就是雙親委派模型

雙親委派模型並不是強制性的約束模型,他只是一個Java設計者推薦的實現方式

工做過程

  • 一個類加載器收到了類加載的請求
  • 他不會本身嘗試加載這個類,而是把這個請求委派給父類加載器去完成
  • 每個類加載器都是如此
  • 當父類加載器完不成加載動做,才讓子類加載器本身去加載

比如這樣的情景:

咱們打仗的時候,另一個陌生的隊伍讓你調兵支援,這個時候你不能擅做主張,要向上級請示,讓上級來進行協調工做,作決定。上級作不了決定,你再嘗試隨機應變,結合實際狀況作決定。

那麼你結合情景想一想,這樣作的好處是啥呢?

好處

不會無組織無紀律。

沒出問題還好,要是遇到這樣的狀況,那就真的危險了。假如你不向上級請示,擅做主張,讓你增援的那個部隊是敵人的,布好陣,就埋伏你,這就要丟性命啊。

或者是上級讓你增援另外一個部隊,由於你離得最近,可是他不知道你不在那個位置,等你接到命令再趕到,友軍屍體都涼了


對應到Java中,就是保證了運行環境不會混亂

由於Java類隨着他的類加載器一塊兒具有了一種帶有優先級的層次關係

例如全部類的父類(除了它自己)"java.lang.Object",有了雙親委派,不管哪個類加載器加載,都是交給最頂層的啓動類加載器加載,這樣他在任何類加載器下都是那一個Object類。

若是沒有雙親委派,我本身定義一個Object類,放到程序的ClassPath中,那麼系統就會出現多個Object,那麼Java體系最基礎的行爲就都沒法保證了。

實現

雙親委派的實現很是簡單

  • 先檢查這個類是否已經被加載過
  • 若是沒有,調用父類加載器的 loadClass()方法
  • 若是父類加載器爲空,則調用啓動類加載器做爲父類加載器
  • 若是父類加載失敗
    • 拋出異常,可是不進行處理
    • 調用本身的findClass()方法加載
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;
    }
}
複製代碼

破壞雙親委派

咱們以前說到,雙親委派模型只是一個推薦模型,並非強制約束,既然是推薦,那我能夠不聽啊。我就是要破壞,不過這裏的破壞並非貶義的,而是對其進行創新。一共大概有三次較大規模的"不聽"

  • 雙親委派出現後爲了保證向前兼容
  • 自身設計缺陷
  • 用戶追求程序動態性

第一次

雙親委派模型是JDK1.2以後纔有的,可是抽象類java.lang.ClassLoader是1.0 就存在了的,面對已經存在的用戶自定義類加載器的實現代碼,Java設計者不得不作出一些妥協。java.lang.ClassLoader中添加了一個findClass方法,原來用戶須要繼承java.lang.ClassLoader類,而後重寫loadClass方法,如今只須要重寫findClass,把本身實現的加載邏輯放到這個方法裏,而不須要重寫loadClass方法。

若是父類加載失敗,則調用本身的findClass方法完成加載,這樣就能夠保證寫出來的加載器的邏輯還是符合雙親委派的。

第二次

雙親委派很好的解決了個各種加載器基礎類的同一問題,可是用戶又想調用用戶本身的代碼怎麼辦

好比咱們的JDBC,是各個廠商本身獨立實現的,Java只是提供一個接口,其餘廠商本身獨立實現,可是啓動類加載器可不認識這個東西。

因此Java設計者引入了一個不太優雅的設計:線程上下文類加載器

這個類加載器經過java.lang.Thread類中的setContextClassLoader()方法進行設置,若是建立線程時還未設置,他將會從父類線程中繼承一個,若是在應用程序的全局範圍內都沒有設置過,那這個類加載器默認就是應用程序類加載器

/** * Sets the context ClassLoader for this Thread. The context * ClassLoader can be set when a thread is created, and allows * the creator of the thread to provide the appropriate class loader, * through {@code getContextClassLoader}, to code running in the thread * when loading classes and resources. * * <p>If a security manager is present, its {@link * SecurityManager#checkPermission(java.security.Permission) checkPermission} * method is invoked with a {@link RuntimePermission RuntimePermission}{@code * ("setContextClassLoader")} permission to see if setting the context * ClassLoader is permitted. * * @param cl * the context ClassLoader for this Thread, or null indicating the * system class loader (or, failing that, the bootstrap class loader) * * @throws SecurityException * if the current thread cannot set the context ClassLoader * * @since 1.2 */
public void setContextClassLoader(ClassLoader cl) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("setContextClassLoader"));
    }
    contextClassLoader = cl;
}
複製代碼

有了這個線程上下文類加載器,JDBC服務使用這個線程上下文類加載器去加載所須要的SPI(Service Provider Interface,接口提供者)代碼,也就是父類加載器請子類加載器去完成類加載的動做。這樣的行爲實際是打通了雙親委派模型的層次結構來逆向使用類加載器,違背了雙親委派的通常性原則。

Java中涉及SPI的加載基本都採用這種方式,如JNDI、JDBC、JCE和JBI

第三次

用戶對程序動態性的追求而致使的。

動態性:指的是當前一些很是熱門的名詞:代碼熱替換,模塊熱部署等等

簡單理解就是但願程序像計算機外設那樣,接上鼠標、U盤,不用重啓就能當即使用,鼠標有問題要換一個,也不須要關機、重啓。這樣的熱部署對企業級軟件開發者有很大的吸引力

像OSGi這種實現模塊化熱部署,已經算是無冕之王了,業界內的Java模塊化標準

他實現的關鍵是它自定義的類加載器機制

  • 每個程序模塊(Bundle)都有一個本身的類加載器
  • 當須要更換一個Bundle時,就把 Bundle 連同類加載器一塊兒換掉以實現代碼的熱替換

OSGi下,類加載器再也不是雙親委派模型中的樹狀結構,而是進一步發展爲更加複雜的網狀結構,當收到類加載請求時,OSGi將按照下面的順序進行類搜索

  1. 將以java.*開頭的類委派給父類加載器加載
  2. 不然,將委派列表名單內的類委派給父類加載器加載
  3. 不然,將Import列表中的類委派給Export這個類的Bundle的類加載器加載
  4. 不然,查找當前Bundle的ClassPath,使用本身的類加載器加載
  5. 不然,查找類是否在本身的Fragment Bundle中,若是在,則委派給Fragment Bundle的類加載器加載
  6. 不然,查找Dynamic Import列表的Bundle,委派給對應的Bundle的類加載器加載
  7. 不然,類查找失敗

可參考的案例

能夠根據實際項目來理解雙親委派模型,能夠參考我下面的博文

比較標準的雙親委派模型——tomcat

異類——OSGi

相關文章
相關標籤/搜索