《深入理解JVM虛擬機》讀書筆記-類加載器&Java模塊化系統(tǒng)

類加載器

一.類加載器

1.1 類與類加載器

類加載器的定義:

  • Java虛擬機設計團隊有意把<font color=red>類加載階段中</font>的“<font color=red>通過一個類的全限定名來獲取描述該類的二進制字節(jié)流</font>”<font color=apple green>這個動作放到Java虛擬機外部去實現(xiàn)</font>,以便<font color=DeepPink>讓應用程序自己決定如何去獲取所需的類</font>旺嬉。實現(xiàn)這個動作的代碼被稱為“類加載器”(Class Loader)。

    • 記憶:類加載階段 通過 這個動作 以便

類加載器雖然只用于實現(xiàn)類的加載動作轴咱,但它在Java程序中起到的作用卻遠超類加載階段:

  • 對于<font color=red>任意一個類</font>违诗,都必須由<font color=green>加載它的類加載器</font>和<font color=green>這個類本身</font>一起<font color=DeepPink>共同確立其在Java虛擬機中的唯一性</font>普碎,每一個<font color=red>類加載器</font>艾帐,都<font color=apple green>擁有一個獨立的類名稱空間</font>。

  • 這句話可以表達得更通俗一些:

    • <font color=apple green>比較兩個類是否“相等”</font>跷敬,<font color=red>只有在這兩個類</font>是由<font color=green>同一個類加載器加載的前提下才有意義</font>讯私,否則,即使這兩個類來源于同一個Class文件西傀,被同一個Java虛擬機加載斤寇,只要加載它們的類加載器不同,那這兩個類就必定不相等拥褂。

    • 類相等在java中的體現(xiàn):

      • 這里所指的“相等”娘锁,包括代表類的Class對象的equals()方法、isAssignableFrom()方法饺鹃、isInstance()方法的返回結果莫秆,

      • 也包括了使用instanceof關鍵字做對象所屬關系判定等各種情況。

如果沒有注意到類加載器的影響尤慰,在某些情況下可能會產生具有迷惑性的結果馏锡,如下代碼演示了不同的類加載器對instanceof關鍵字運算的結果的影響雷蹂。


/*

 * 類加載器與instanceof關鍵字演示

 *

 * @author zzm

 */

public class ClassLoaderTest {

    public static void main(String[] args) throws Exception {

        //自定義加載器

        ClassLoader myLoader = new ClassLoader() {

            @Override

            public Class<?> loadClass(String name) throws ClassNotFoundException {

                try {

                    String fileName = name.substring(name.lastIndexOf(".") + 1)+".class";

                    InputStream is = getClass().getResourceAsStream(fileName);

                    if (is == null) {

                        return super.loadClass(name);

                    }

                    byte[] b = new byte[is.available()];

                    is.read(b);

                    return defineClass(name, b, 0, b.length);

                } catch (IOException e) {

                    throw new ClassNotFoundException(name);

                }

            }

        };

        Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();

        System.out.println(obj.getClass());

        System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest);

    }

}

輸出


class org.fenixsoft.classloading.ClassLoaderTest

false

? 兩行輸出結果中伟端,從第一行可以看到這個對象確實是類org.fenixsoft.classloading.ClassLoaderTest實例化出來的,但在第二行的輸出中卻發(fā)現(xiàn)這個對象與類org.fenixsoft.classloading.ClassLoaderTest做所屬類型檢查的時候返回了false匪煌。這是因為Java虛擬機中同時存在了兩個ClassLoaderTest類责蝠,一個是由虛擬機的應用程序類加載器所加載的,另外一個是由我們自定義的類加載器加載的萎庭,雖然它們都來自同一個Class文件霜医,但在Java虛擬機中仍然是兩個互相獨立的類,做對象所屬類型檢查時的結果自然為false驳规。

1.2 雙親委派模型

1.2.1 類加載器的分類:

  • 粗分類:

    • <font color=apple green>站在Java虛擬機的角度來看</font>肴敛,只存在兩種不同的類加載器:

      • 一種是<font color=red>啟動類加載器</font>(Bootstrap ClassLoader),<font color=DeepPink>這個類加載器使用C++語言實現(xiàn)吗购,是虛擬機自身的一部分</font>医男;

      • 另外一種就是<font color=red>其他所有的類加載器</font>,這些<font color=DeepPink>類加載器都由Java語言實現(xiàn)</font>捻勉,<font color=DeepPink>獨立存在于虛擬機外部</font>镀梭,并且<font color=DeepPink>全都繼承自抽象類java.lang.ClassLoader</font>。

  • 細分類:

    • 站在Java開發(fā)人員的角度來看踱启,類加載器就應當劃分得更細致一些报账,即三層類加載器(JDK 8及之前版本絕大多數(shù)Java程序都會使用到以下3個系統(tǒng)提供的類加載器來進行加載)<<font color=purple>見補充說明1</font>>:

      • 1.啟動類加載器(Bootstrap Class Loader):

        • 存放位置:

          • 這個類加載器負責加載存放在<font color=red><JAVA_HOME>\lib目錄</font>研底,或者被-Xbootclasspath參數(shù)所指定的路徑中存放的,而且是<font color=DeepPink>Java虛擬機能夠識別的類庫加載到虛擬機的內存中</font>透罢。

            • 按照文件名識別榜晦,如rt.jar、tools.jar羽圃,名字不符合的類庫即使放在lib目錄中也不會被加載芽隆。
        • 引用:

          • <font color=DeepPink>啟動類加載器無法被Java程序直接引用</font>,用戶在編寫自定義類加載器時统屈,如果需要把加載請求委派給引導類加載器去處理胚吁,那直接使用null代替即可<<font color=purple>見補充說明2</font>>。
      • 2.擴展類加載器(Extension Class Loader):

        • 實現(xiàn)方式:

          • 這個類加載器是在類<font color=red>sun.misc.Launcher$ExtClassLoader</font>中以<font color=red>Java代碼的形式實現(xiàn)的</font>愁憔。
        • 加載路徑:

          • 它負責加載<font color=red><JAVA_HOME>\lib\ext</font>目錄中腕扶,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中所有的類庫。
        • 作用:

          • 擴展:根據(jù)“擴展類加載器”這個名稱吨掌,就可以推斷出這是一種Java系統(tǒng)類庫的擴展機制半抱,JDK的開發(fā)團隊允許<font color=DeepPink>用戶將具有通用性的類庫放置在ext目錄里以擴展Java SE的功能</font>。

            • 在JDK 9之后膜宋,這種擴展機制被模塊化帶來的天然的擴展能力所取代窿侈。由于擴展類加載器是由Java代碼實現(xiàn)的,開發(fā)者可以直接在程序中使用擴展類加載器來加載Class文件秋茫。
      • 3.應用程序類加載器(Application Class Loader):

        • 實現(xiàn)方式:

          • 這個類加載器由<font color=red>sun.misc.Launcher$AppClassLoader</font>來實現(xiàn)史简。

          • 由于應用程序類加載器是ClassLoader類中的getSystem-ClassLoader()方法的返回值,所以有些場合中也稱它為“系統(tǒng)類加載器”肛著。

        • 作用:

          • 它負責<font color=DeepPink>加載用戶類路徑(ClassPath)上所有的類庫</font>圆兵,開發(fā)者同樣可以直接在代碼中使用這個類加載器。

          • 如果應用程序中沒有自定義過自己的類加載器枢贿,一般情況下這個就是程序中默認的類加載器殉农。


補充說明1:

? <font color=green>自JDK 1.2以來</font>,Java一直<font color=apple green>保持著三層類加載器局荚、雙親委派的類加載架構</font>超凳,盡管這套架構在Java模塊化(1.9)系統(tǒng)出現(xiàn)后有了一些調整變動,但依然未改變其主體結構耀态,我們將在后面的章節(jié)中專門討論模塊化系統(tǒng)下的類加載器轮傍。

補充說明2:


/*

Returns the class loader for the class.  Some implementations may use null to represent the bootstrap class loader. This method will return  null in such implementations if this class was loaded by the bootstrap class loader.

*/

public ClassLoader getClassLoader() {

    ClassLoader cl = getClassLoader0();

    if (cl == null)

        return null;

    SecurityManager sm = System.getSecurityManager();

    if (sm != null) {

        ClassLoader ccl = ClassLoader.getCallerClassLoader();

        if (ccl != null && ccl != cl && !cl.isAncestor(ccl)) {

            sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);

        }

    }

    return cl;

}

以上展示的就是java.lang.ClassLoader.getClassLoader()方法的代碼片段,其中的注釋和代碼實現(xiàn)都明確地說明了以null值來代表引導類加載器的約定規(guī)則茫陆。


1.2.2 雙親委派模型

image.png

? 如上圖中展示的各種類加載器之間的層次關系被稱為類加載器的“雙親委派模型(Parents Delegation Model)”金麸。

雙親委派模型的規(guī)則

  • 雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器簿盅。

類加載器之間的父子關系的實現(xiàn)方式

  • 這里類加載器之間的父子關系一般不是以繼承(Inheritance)的關系來實現(xiàn)的挥下,而是通常使用組合(Composition)關系來復用父加載器的代碼揍魂。

    • 前面描述這種類加載器協(xié)作關系時,專門用雙引號強調這是“通撑镂粒”的協(xié)作關系:

      • 類加載器的雙親委派模型在JDK 1.2時期被引入现斋,并被廣泛應用于此后幾乎所有的Java程序中,但<font color=red>它并不是一個具有強制性約束力的模型</font>偎蘸,而是Java設計者們推薦給開發(fā)者的一種類加載器實現(xiàn)的最佳實踐庄蹋。

雙親委派模型的工作過程是

  • 如果<font color=green>一個類加載器收到了類加載的請求</font>,它<font color=apple green>首先不會自己去嘗試加載這個類</font>迷雪,而是<font color=apple green>把這個請求委派給父類加載器去完成</font>限书,<font color=red>每一個層次的類加載器都是如此</font>,因此<font color=DeepPink>所有的加載請求最終都應該傳送到最頂層的啟動類加載器中</font>章咧,<font color=green>只有當父加載器反饋自己無法完成這個加載請求(它的搜索范圍中沒有找到所需的類)時</font>倦西,<font color=apple green>子加載器才會嘗試自己去完成加載</font>。

雙親委派模型的好處

  • 使用雙親委派模型來組織類加載器之間的關系赁严,一個顯而易見的好處就是<font color=DeepPink>Java中的類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關系</font>扰柠。

  • 例如類java.lang.Object,它存放在rt.jar之中疼约,無論哪一個類加載器要加載這個類卤档,最終都是委派給處于模型最頂端的啟動類加載器進行加載,因此Object類在程序的各種類加載器環(huán)境中都能夠保證是同一個類程剥。

    • 即啟動類加載器的加載順序高于其他加載器劝枣,能夠保證jvm需要的類的唯一性。
  • 反之倡缠,如果沒有使用雙親委派模型哨免,都由各個類加載器自行去加載的話,如果用戶自己也編寫了一個名為java.lang.Object的類昙沦,并放在程序的ClassPath中,那系統(tǒng)中就會出現(xiàn)多個不同的Object類载荔,Java類型體系中最基礎的行為也就無從保證盾饮,應用程序將會變得一片混亂。

例子:


protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException

{

    // 首先懒熙,檢查請求的類是否已經被加載過了

    Class c = findLoadedClass(name);

    if (c == null) {

        try {

        if (parent != null) {

            c = parent.loadClass(name, false);

        } else {

            c = findBootstrapClassOrNull(name);

        }

        } catch (ClassNotFoundException e) {

            // 如果父類加載器拋出ClassNotFoundException

            // 說明父類加載器無法完成加載請求

        }

        if (c == null) {

            // 在父類加載器無法加載時

            // 再調用本身的findClass方法來進行類加載

            c = findClass(name);

        }

    }

    if (resolve) {

        resolveClass(c);

    }

    return c;

}

? 這段代碼的邏輯清晰易懂:先檢查請求加載的類型是否已經被加載過丘损,若沒有則調用父加載器的loadClass()方法,若父加載器為空則默認使用啟動類加載器作為父加載器工扎。假如父類加載器加載失敗徘钥,拋出ClassNotFoundException異常的話,才調用自己的findClass()方法嘗試進行加載肢娘。

1.2.3 破壞雙親委派模型(選看)

? 上文提到過雙親委派模型并不是一個具有強制性約束的模型呈础,而是Java設計者推薦給開發(fā)者們的類加載器實現(xiàn)方式舆驶。在Java的世界中大部分的類加載器都遵循這個模型,但也有例外的情況而钞,直到Java模塊化出現(xiàn)為止沙廉,雙親委派模型主要出現(xiàn)過3次較大規(guī)模“被破壞”的情況臼节。

破壞該模型的歷史:

  • 雙親委派模型的第一次“被破壞”其實發(fā)生在雙親委派模型出現(xiàn)之前——即JDK 1.2面世以前的“遠古”時代撬陵。

    • 由于雙親委派模型在JDK 1.2之后才被引入,但是類加載器的概念和抽象類java.lang.ClassLoader則在Java的第一個版本中就已經存在网缝,面對已經存在的用戶自定義類加載器的代碼巨税,Java設計者們引入雙親委派模型時不得不做出一些妥協(xié),為了兼容這些已有代碼粉臊,無法再以技術手段避免loadClass()被子類覆蓋的可能性垢夹,只能在JDK 1.2之后的java.lang.ClassLoader中添加一個新的protected方法findClass(),并引導用戶編寫的類加載邏輯時盡可能去重寫這個方法维费,而不是在loadClass()中編寫代碼果元。上節(jié)我們已經分析過loadClass()方法,雙親委派的具體邏輯就實現(xiàn)在這里面犀盟,按照loadClass()方法的邏輯而晒,如果父類加載失敗,會自動調用自己的findClass()方法來完成加載阅畴,這樣既不影響用戶按照自己的意愿去加載類倡怎,又可以保證新寫出來的類加載器是符合雙親委派規(guī)則的。
  • 雙親委派模型的第二次“被破壞”是由這個模型自身的缺陷導致的

    • 雙親委派很好地解決了各個類加載器協(xié)作時基礎類型的一致性問題(越基礎的類由越上層的加載器進行加載)贱枣,基礎類型之所以被稱為“基礎”监署,是因為它們總是作為被用戶代碼繼承、調用的API存在纽哥,但程序設計往往沒有絕對不變的完美規(guī)則钠乏,如果有基礎類型又要調用回用戶的代碼,那該怎么辦呢春塌?

      這并非是不可能出現(xiàn)的事情晓避,一個典型的例子便是JNDI服務,JNDI現(xiàn)在已經是Java的標準服務只壳,它的代碼由啟動類加載器來完成加載(在JDK 1.3時加入到rt.jar的)俏拱,肯定屬于Java中很基礎的類型了。但JNDI存在的目的就是對資源進行查找和集中管理吼句,它需要調用由其他廠商實現(xiàn)并部署在應用程序的ClassPath下的JNDI服務提供者接口(Service Provider Interface锅必,SPI)的代碼

      • 現(xiàn)在問題來了,啟動類加載器是絕不可能認識惕艳、加載這些代碼的搞隐,那該怎么辦驹愚?

        • 為了解決這個困境,Java的設計團隊只好引入了一個不太優(yōu)雅的設計:線程上下文類加載器(Thread Context ClassLoader)尔许。這個類加載器可以通過java.lang.Thread類的setContext-ClassLoader()方法進行設置么鹤,如果創(chuàng)建線程時還未設置,它將會從父線程中繼承一個味廊,如果在應用程序的全局范圍內都沒有設置過的話蒸甜,那這個類加載器默認就是應用程序類加載器。

        • 有了線程上下文類加載器余佛,程序就可以做一些“舞弊”的事情了柠新。JNDI服務使用這個線程上下文類加載器去加載所需的SPI服務代碼,這是一種父類加載器去請求子類加載器完成類加載的行為辉巡,這種行為實際上是打通了雙親委派模型的層次結構來逆向使用類加載器恨憎,已經違背了雙親委派模型的一般性原則,但也是無可奈何的事情郊楣。Java中涉及SPI的加載基本上都采用這種方式來完成憔恳,例如JNDI、JDBC净蚤、JCE钥组、JAXB和JBI等。不過今瀑,當SPI的服務提供者多于一個的時候程梦,代碼就只能根據(jù)具體提供者的類型來硬編碼判斷,為了消除這種極不優(yōu)雅的實現(xiàn)方式橘荠,在JDK 6時屿附,JDK提供了java.util.ServiceLoader類,以META-INF/services中的配置信息哥童,輔以責任鏈模式挺份,這才算是給SPI的加載提供了一種相對合理的解決方案。

  • 雙親委派模型的第三次“被破壞”是由于用戶對程序動態(tài)性的追求而導致的

    • 這里所說的“動態(tài)性”指的是一些非橙缪粒“熱”門的名詞:代碼熱替換(Hot Swap)压恒、模塊熱部署(Hot Deployment)等。說白了就是希望Java應用程序能像我們的電腦外設那樣错邦,接上鼠標、U盤型宙,不用重啟機器就能立即使用撬呢,鼠標有問題或要升級就換個鼠標,不用關機也不用重啟妆兑。對于個人電腦來說魂拦,重啟一次其實沒有什么大不了的毛仪,但對于一些生產系統(tǒng)來說,關機重啟一次可能就要被列為生產事故芯勘,這種情況下熱部署就對軟件開發(fā)者箱靴,尤其是大型系統(tǒng)或企業(yè)級軟件開發(fā)者具有很大的吸引力。

Java社區(qū)關于模塊化規(guī)范的歷史:

  • 早在2008年荷愕,在Java社區(qū)關于模塊化規(guī)范的第一場戰(zhàn)役里衡怀,由Sun/Oracle公司所提出的JSR-294、JSR-277規(guī)范提案就曾敗給以IBM公司主導的JSR-291(即OSGi R4.2)提案安疗。盡管Sun/Oracle并不甘心就此失去Java模塊化的主導權抛杨,隨即又再拿出Jigsaw項目迎戰(zhàn),但此時OSGi已經站穩(wěn)腳跟荐类,成為業(yè)界“事實上”的Java模塊化標準怖现。曾經在很長一段時間內,IBM憑借著OSGi廣泛應用基礎讓Jigsaw吃盡苦頭玉罐,其影響一直持續(xù)到Jigsaw隨JDK 9面世才算告一段落屈嗤。而且即使Jigsaw現(xiàn)在已經是Java的標準功能了,它仍需小心翼翼地避開OSGi運行期動態(tài)熱部署上的優(yōu)勢吊输,僅局限于靜態(tài)地解決模塊間封裝隔離和訪問控制的問題饶号,,現(xiàn)在我們先來簡單看一看OSGi是如何通過類加載器實現(xiàn)熱部署的:

    • OSGi實現(xiàn)模塊化熱部署的關鍵是它自定義的類加載器機制的實現(xiàn)璧亚,每一個程序模塊(OSGi中稱為Bundle)都有一個自己的類加載器讨韭,當需要更換一個Bundle時,就把Bundle連同類加載器一起換掉以實現(xiàn)代碼的熱替換癣蟋。在OSGi環(huán)境下透硝,類加載器不再雙親委派模型推薦的樹狀結構,而是進一步發(fā)展為更加復雜的網(wǎng)狀結構疯搅,當收到類加載請求時濒生,OSGi將按照下面的順序進行類搜索:

      1)將以java.*開頭的類,委派給父類加載器加載幔欧。

      2)否則罪治,將委派列表名單內的類,委派給父類加載器加載礁蔗。

      3)否則觉义,將Import列表中的類,委派給Export這個類的Bundle的類加載器加載浴井。

      4)否則晒骇,查找當前Bundle的ClassPath,使用自己的類加載器加載。

      5)否則洪囤,查找類是否在自己的Fragment Bundle中徒坡,如果在,則委派給Fragment Bundle的類加載器加載瘤缩。

      6)否則喇完,查找Dynamic Import列表的Bundle,委派給對應Bundle的類加載器加載剥啤。

      7)否則锦溪,類查找失敗。

      上面的查找順序中只有開頭兩點仍然符合雙親委派模型的原則铐殃,其余的類查找都是在平級的類加載器中進行的海洼,關于OSGi的其他內容,筆者就不再展開了富腊。

? 本節(jié)中雖然使用了“被破壞”這個詞來形容上述不符合雙親委派模型原則的行為坏逢,但這里“被破壞”并不一定是帶有貶義的。只要有明確的目的和充分的理由赘被,突破舊有原則無疑是一種創(chuàng)新是整。正如OSGi中的類加載器的設計不符合傳統(tǒng)的雙親委派的類加載器架構,且業(yè)界對其為了實現(xiàn)熱部署而帶來的額外的高復雜度還存在不少爭議民假,但對這方面有了解的技術人員基本還是能達成一個共識浮入,認為OSGi中對類加載器的運用是值得學習的,完全弄懂了OSGi的實現(xiàn)羊异,就算是掌握了類加載器的精粹事秀。

二.Java模塊化系統(tǒng)

  • 引入的版本:

    • 在JDK 9中引入的Java模塊化系統(tǒng)(Java Platform Module System,JPMS)是對Java技術的一次重要升級野舶,為了能夠實現(xiàn)模塊化的關鍵目標——<font color=red>可配置的封裝隔離機制</font>易迹,Java虛擬機對類加載架構也做出了相應的變動調整,才使模塊化系統(tǒng)得以順利地運作。
  • Java的模塊定義還包含以下內容:

    • 依賴其他模塊的列表。

    • 導出的包列表瓷翻,即其他模塊可以使用的列表。

    • 開放的包列表菌湃,即其他模塊可反射訪問模塊的列表。

    • 使用的服務列表。

    • 提供服務的實現(xiàn)列表。

  • 模塊化-可配置的封裝隔離機制 帶來的好處:

    • <font color=Fuchsia>避免了很大一部分由于類型依賴而引發(fā)的運行時異常</font>

      • <font color=Fuchsia>JDK9之前因為基于路徑加載類闸衫,一旦類路徑缺失對應依賴,那就只能等程序發(fā)生該類型的加載诽嘉、鏈接時才能報錯</font>

        • <font color=DeepPink>在JDK9之前是基于類路徑(ClassPath)來查找依賴的</font>楚堤,<font color=green>如果類路徑中缺失了運行時依賴的類型</font>疫蔓,那就<font color=red>只能等</font>程序運行到發(fā)生<font color=apple green>該類型的加載含懊、鏈接時才會報出運行的異常</font>身冬。
      • <font color=Fuchsia>JDK9之后如果啟用模塊化進行封裝,模塊可以聲明對其他模塊的顯示依賴岔乔,讓JVM在啟動時驗證依賴關系是否完備酥筝,如果缺失則直接報錯</font>

        • 而在JDK 9以后,<font color=green>如果啟用了模塊化進行封裝</font>雏门,<font color=apple green>模塊就可以聲明對其他模塊的顯式依賴</font>嘿歌,這樣<font color=red>Java虛擬機</font>就能夠在<font color=DeepPink>啟動時驗證應用程序開發(fā)階段設定好的依賴關系在運行期是否完備,如有缺失那就直接啟動失敗</font>茁影,從而避免了很大一部分由于類型依賴而引發(fā)的運行時異常宙帝。
    • <font color=Fuchsia>對于public類型,模塊提供了更精細的可訪問性控制募闲,必須明確聲明哪些public類型可以被哪些模塊訪問步脓,否則不能讓所有代碼訪問到public</font>

      • 可配置的封裝隔離機制還解決了原來類路徑上跨JAR文件的public類型的可訪問性問題。

      • <font color=apple green>JDK 9中的public類型不再意味著程序的所有地方的代碼都可以隨意訪問到它們</font>浩螺,<font color=green>模塊提供了更精細的可訪問性控制靴患,必須明確聲明其中哪一些public的類型可以被其他哪一些模塊訪問</font>,<font color=DeepPink>這種訪問控制也主要是在類加載過程中完成的</font>要出,具體內容筆者在前文對解析階段的講解中已經介紹過鸳君。

2.1 模塊兼容性

這里講的兼容性,主要指以下特性:

  • 兼容:傳統(tǒng)的類路徑查找機制

    • <font color=DeepPink>為了使可配置的封裝隔離機制能夠兼容傳統(tǒng)的類路徑查找機制</font>患蹂,JDK 9提出了與“類路徑”(ClassPath)相對應的“模塊路徑”(ModulePath)的概念或颊。

      • 簡單來說,就是某個類庫到底是模塊還是傳統(tǒng)的JAR包传于,只取決于它存放在哪種路徑上囱挑。

        • 只要是放在類路徑上的JAR文件,無論其中是否包含模塊化信息(是否包含了module-info.class文件)格了,它都會被當作傳統(tǒng)的JAR包來對待看铆;

        • 相應地,只要放在模塊路徑上的JAR文件盛末,即使沒有使用JMOD后綴弹惦,甚至說其中并不包含module-info.class文件,它也仍然會被當作一個模塊來對待悄但。

    • 記憶版:

      • <font color=Fuchsia>為了能夠兼容傳統(tǒng)的類路徑查找機制棠隐,JDK 9提出了與“類路徑”(ClassPath)相對應的“模塊路徑”(ModulePath)的概念,某個類庫到底是模塊還是傳統(tǒng)的JAR包檐嚣,只取決于它存放在哪種路徑上</font>助泽。
    • JDK9通過如下3條規(guī)則保證了即使Java應用依然使用傳統(tǒng)的類路徑啰扛,升級到JDK 9對應用來說幾乎不會有任何感覺,項目也不需要專門為了升級JDK版本而去把傳統(tǒng)JAR包升級成模塊

      • JAR文件在類路徑的訪問規(guī)則:所有類路徑下的JAR文件及其他資源文件嗡贺,都被視為自動打包在一個匿名模塊(Unnamed Module)里隐解,這個匿名模塊幾乎是沒有任何隔離的,它可以看到和使用類路徑上所有的包诫睬、JDK系統(tǒng)模塊中所有的導出包煞茫,以及模塊路徑上所有模塊中導出的包。

      • 模塊在模塊路徑的訪問規(guī)則:模塊路徑下的具名模塊(Named Module)只能訪問到它依賴定義中列明依賴的模塊和包摄凡,匿名模塊里所有的內容對具名模塊來說都是不可見的续徽,即具名模塊看不見傳統(tǒng)JAR包的內容。

      • JAR文件在模塊路徑的訪問規(guī)則:如果把一個傳統(tǒng)的亲澡、不包含模塊定義的JAR文件放置到模塊路徑中钦扭,它就會變成一個自動模塊(Automatic Module)。盡管不包含module-info.class床绪,但自動模塊將默認依賴于整個模塊路徑中的所有模塊客情,因此可以訪問到所有模塊導出的包,自動模塊也默認導出自己所有的包会涎。

  • 不兼容:Java模塊化系統(tǒng)目前不支持在模塊定義中加入版本號來管理和約束依賴裹匙,本身也不支持多版本號的概念和版本選擇功能。

2.2 模塊化下的類加載器

? 為了保證兼容性末秃,JDK 9并沒有從根本上動搖從JDK 1.2以來運行了二十年之久的三層類加載器架構以及雙親委派模型概页。但是為了模塊化系統(tǒng)的順利施行,模塊化下的類加載器仍然發(fā)生了一些應該被注意到變動练慕,主要包括以下幾個方面:

  • 1.擴展類加載器(Extension Class Loader)被平臺類加載器(Platform Class Loader)取代惰匙。

    • 這其實是一個很順理成章的變動,既然整個JDK都基于模塊化進行構建(原來的rt.jar和tools.jar被拆分成數(shù)十個JMOD文件)铃将,其中的Java類庫就已天然地滿足了可擴展的需求

      • 那自然無須再保留<JAVA_HOME>\lib\ext目錄项鬼,此前使用這個目錄或者java.ext.dirs系統(tǒng)變量來擴展JDK功能的機制已經沒有繼續(xù)存在的價值了,用來加載這部分類庫的擴展類加載器也完成了它的歷史使命劲阎。
    • 類似地绘盟,在新版的JDK中也取消了<JAVA_HOME>\jre目錄,因為隨時可以組合構建出程序運行所需的JRE來

      • 譬如假設我們只使用java.base模塊中的類型悯仙,那么隨時可以通過以下命令打包出一個“JRE”

        
        jlink -p $JAVA_HOME/jmods --add-modules java.base --output jre
        
        
  • 2.平臺類加載器和應用程序類加載器都不再派生自java.net.URLClassLoader

    • 如果有程序直接依賴了這種繼承關系龄毡,或者依賴了URLClassLoader類的特定方法,那代碼很可能會在JDK 9及更高版本的JDK中崩潰锡垄。

    • 現(xiàn)在啟動類加載器沦零、平臺類加載器、應用程序類加載器全都繼承于jdk.internal.loader.BuiltinClassLoader货岭,在BuiltinClassLoader中實現(xiàn)了新的模塊化架構下類如何從模塊中加載的邏輯路操,以及模塊中資源可訪問性的處理疾渴。


            jdk9之前的類加載器繼承架構
      
image.png

JDK 9及以后的類加載器繼承架構

image.png

  • 3.啟動類加載器現(xiàn)在是在Java虛擬機內部和Java類庫共同協(xié)作實現(xiàn)的類加載器,盡管有了BootClassLoader這樣的Java類屯仗,但為了與之前的代碼保持兼容搞坝,所有在獲取啟動類加載器的場景(譬如Object.class.getClassLoader())中仍然會返回null來代替,而不會得到BootClassLoader的實例祭钉。
image.png
  • 4.JDK 9中雖然仍然維持著三層類加載器和雙親委派的架構瞄沙,但類加載的委派關系也發(fā)生了變動。

    • 當平臺及應用程序類加載器收到類加載請求慌核,在委派給父加載器加載前,要先判斷該類是否能夠歸屬到某一個系統(tǒng)模塊中申尼,如果可以找到這樣的歸屬關系垮卓,就要優(yōu)先委派給負責那個模塊的加載器完成加載,也許這可以算是對雙親委派的第四次破壞师幕。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末粟按,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霹粥,更是在濱河造成了極大的恐慌灭将,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件后控,死亡現(xiàn)場離奇詭異庙曙,居然都是意外死亡,警方通過查閱死者的電腦和手機浩淘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門捌朴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人张抄,你說我怎么就攤上這事砂蔽。” “怎么了署惯?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵左驾,是天一觀的道長。 經常有香客問我极谊,道長诡右,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任怀酷,我火速辦了婚禮稻爬,結果婚禮上,老公的妹妹穿的比我還像新娘蜕依。我一直安慰自己桅锄,他們只是感情好琉雳,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著友瘤,像睡著了一般翠肘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辫秧,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天束倍,我揣著相機與錄音,去河邊找鬼盟戏。 笑死绪妹,一個胖子當著我的面吹牛,可吹牛的內容都是我干的柿究。 我是一名探鬼主播邮旷,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蝇摸!你這毒婦竟也來了婶肩?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤貌夕,失蹤者是張志新(化名)和其女友劉穎律歼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啡专,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡险毁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了植旧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辱揭。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖病附,靈堂內的尸體忽然破棺而出问窃,到底是詐尸還是另有隱情,我是刑警寧澤完沪,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布域庇,位于F島的核電站,受9級特大地震影響覆积,放射性物質發(fā)生泄漏听皿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一宽档、第九天 我趴在偏房一處隱蔽的房頂上張望尉姨。 院中可真熱鬧,春花似錦吗冤、人聲如沸又厉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽覆致。三九已至侄旬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間煌妈,已是汗流浹背儡羔。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留璧诵,地道東北人汰蜘。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像腮猖,于是被迫代替她去往敵國和親鉴扫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容