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

類加載器

一.類加載器

1.1 類與類加載器

類加載器的定義:

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

    • 記憶:類加載階段 通過 這個(gè)動(dòng)作 以便

類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但它在Java程序中起到的作用卻遠(yuǎn)超類加載階段:

  • 對(duì)于<font color=red>任意一個(gè)類</font>,都必須由<font color=green>加載它的類加載器</font>和<font color=green>這個(gè)類本身</font>一起<font color=DeepPink>共同確立其在Java虛擬機(jī)中的唯一性</font>,每一個(gè)<font color=red>類加載器</font>,都<font color=apple green>擁有一個(gè)獨(dú)立的類名稱空間</font>。

  • 這句話可以表達(dá)得更通俗一些:

    • <font color=apple green>比較兩個(gè)類是否“相等”</font>,<font color=red>只有在這兩個(gè)類</font>是由<font color=green>同一個(gè)類加載器加載的前提下才有意義</font>,否則,即使這兩個(gè)類來源于同一個(gè)Class文件,被同一個(gè)Java虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等。

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

      • 這里所指的“相等”,包括代表類的Class對(duì)象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結(jié)果,

      • 也包括了使用instanceof關(guān)鍵字做對(duì)象所屬關(guān)系判定等各種情況。

如果沒有注意到類加載器的影響,在某些情況下可能會(huì)產(chǎn)生具有迷惑性的結(jié)果,如下代碼演示了不同的類加載器對(duì)instanceof關(guān)鍵字運(yùn)算的結(jié)果的影響。


/*

 * 類加載器與instanceof關(guān)鍵字演示

 *

 * @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

? 兩行輸出結(jié)果中,從第一行可以看到這個(gè)對(duì)象確實(shí)是類org.fenixsoft.classloading.ClassLoaderTest實(shí)例化出來的,但在第二行的輸出中卻發(fā)現(xiàn)這個(gè)對(duì)象與類org.fenixsoft.classloading.ClassLoaderTest做所屬類型檢查的時(shí)候返回了false。這是因?yàn)镴ava虛擬機(jī)中同時(shí)存在了兩個(gè)ClassLoaderTest類,一個(gè)是由虛擬機(jī)的應(yīng)用程序類加載器所加載的,另外一個(gè)是由我們自定義的類加載器加載的,雖然它們都來自同一個(gè)Class文件,但在Java虛擬機(jī)中仍然是兩個(gè)互相獨(dú)立的類,做對(duì)象所屬類型檢查時(shí)的結(jié)果自然為false。

1.2 雙親委派模型

1.2.1 類加載器的分類:

  • 粗分類:

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

      • 一種是<font color=red>啟動(dòng)類加載器</font>(Bootstrap ClassLoader),<font color=DeepPink>這個(gè)類加載器使用C++語言實(shí)現(xiàn),是虛擬機(jī)自身的一部分</font>;

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

  • 細(xì)分類:

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

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

        • 存放位置:

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

            • 按照文件名識(shí)別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄中也不會(huì)被加載。
        • 引用:

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

        • 實(shí)現(xiàn)方式:

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

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

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

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

        • 實(shí)現(xiàn)方式:

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

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

        • 作用:

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

          • 如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。


補(bǔ)充說明1:

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

補(bǔ)充說明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()方法的代碼片段,其中的注釋和代碼實(shí)現(xiàn)都明確地說明了以null值來代表引導(dǎo)類加載器的約定規(guī)則。


1.2.2 雙親委派模型

image.png

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

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

  • 雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)有自己的父類加載器。

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

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

    • 前面描述這種類加載器協(xié)作關(guān)系時(shí),專門用雙引號(hào)強(qiáng)調(diào)這是“通?!钡膮f(xié)作關(guān)系:

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

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

  • 如果<font color=green>一個(gè)類加載器收到了類加載的請(qǐng)求</font>,它<font color=apple green>首先不會(huì)自己去嘗試加載這個(gè)類</font>,而是<font color=apple green>把這個(gè)請(qǐng)求委派給父類加載器去完成</font>,<font color=red>每一個(gè)層次的類加載器都是如此</font>,因此<font color=DeepPink>所有的加載請(qǐng)求最終都應(yīng)該傳送到最頂層的啟動(dòng)類加載器中</font>,<font color=green>只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒有找到所需的類)時(shí)</font>,<font color=apple green>子加載器才會(huì)嘗試自己去完成加載</font>。

雙親委派模型的好處

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

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

    • 即啟動(dòng)類加載器的加載順序高于其他加載器,能夠保證jvm需要的類的唯一性。
  • 反之,如果沒有使用雙親委派模型,都由各個(gè)類加載器自行去加載的話,如果用戶自己也編寫了一個(gè)名為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中就會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基礎(chǔ)的行為也就無從保證,應(yīng)用程序?qū)?huì)變得一片混亂。

例子:


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

{

    // 首先,檢查請(qǐng)求的類是否已經(jīng)被加載過了

    Class c = findLoadedClass(name);

    if (c == null) {

        try {

        if (parent != null) {

            c = parent.loadClass(name, false);

        } else {

            c = findBootstrapClassOrNull(name);

        }

        } catch (ClassNotFoundException e) {

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

            // 說明父類加載器無法完成加載請(qǐng)求

        }

        if (c == null) {

            // 在父類加載器無法加載時(shí)

            // 再調(diào)用本身的findClass方法來進(jìn)行類加載

            c = findClass(name);

        }

    }

    if (resolve) {

        resolveClass(c);

    }

    return c;

}

? 這段代碼的邏輯清晰易懂:先檢查請(qǐng)求加載的類型是否已經(jīng)被加載過,若沒有則調(diào)用父加載器的loadClass()方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。假如父類加載器加載失敗,拋出ClassNotFoundException異常的話,才調(diào)用自己的findClass()方法嘗試進(jìn)行加載。

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

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

破壞該模型的歷史:

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

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

    • 雙親委派很好地解決了各個(gè)類加載器協(xié)作時(shí)基礎(chǔ)類型的一致性問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類型之所以被稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a繼承、調(diào)用的API存在,但程序設(shè)計(jì)往往沒有絕對(duì)不變的完美規(guī)則,如果有基礎(chǔ)類型又要調(diào)用回用戶的代碼,那該怎么辦呢?

      這并非是不可能出現(xiàn)的事情,一個(gè)典型的例子便是JNDI服務(wù),JNDI現(xiàn)在已經(jīng)是Java的標(biāo)準(zhǔn)服務(wù),它的代碼由啟動(dòng)類加載器來完成加載(在JDK 1.3時(shí)加入到rt.jar的),肯定屬于Java中很基礎(chǔ)的類型了。但JNDI存在的目的就是對(duì)資源進(jìn)行查找和集中管理,它需要調(diào)用由其他廠商實(shí)現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI服務(wù)提供者接口(Service Provider Interface,SPI)的代碼

      • 現(xiàn)在問題來了,啟動(dòng)類加載器是絕不可能認(rèn)識(shí)、加載這些代碼的,那該怎么辦?

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

        • 有了線程上下文類加載器,程序就可以做一些“舞弊”的事情了。JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需的SPI服務(wù)代碼,這是一種父類加載器去請(qǐng)求子類加載器完成類加載的行為,這種行為實(shí)際上是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,已經(jīng)違背了雙親委派模型的一般性原則,但也是無可奈何的事情。Java中涉及SPI的加載基本上都采用這種方式來完成,例如JNDI、JDBC、JCE、JAXB和JBI等。不過,當(dāng)SPI的服務(wù)提供者多于一個(gè)的時(shí)候,代碼就只能根據(jù)具體提供者的類型來硬編碼判斷,為了消除這種極不優(yōu)雅的實(shí)現(xiàn)方式,在JDK 6時(shí),JDK提供了java.util.ServiceLoader類,以META-INF/services中的配置信息,輔以責(zé)任鏈模式,這才算是給SPI的加載提供了一種相對(duì)合理的解決方案。

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

    • 這里所說的“動(dòng)態(tài)性”指的是一些非?!盁帷遍T的名詞:代碼熱替換(Hot Swap)、模塊熱部署(Hot Deployment)等。說白了就是希望Java應(yīng)用程序能像我們的電腦外設(shè)那樣,接上鼠標(biāo)、U盤,不用重啟機(jī)器就能立即使用,鼠標(biāo)有問題或要升級(jí)就換個(gè)鼠標(biāo),不用關(guān)機(jī)也不用重啟。對(duì)于個(gè)人電腦來說,重啟一次其實(shí)沒有什么大不了的,但對(duì)于一些生產(chǎn)系統(tǒng)來說,關(guān)機(jī)重啟一次可能就要被列為生產(chǎn)事故,這種情況下熱部署就對(duì)軟件開發(fā)者,尤其是大型系統(tǒng)或企業(yè)級(jí)軟件開發(fā)者具有很大的吸引力。

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

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

    • OSGi實(shí)現(xiàn)模塊化熱部署的關(guān)鍵是它自定義的類加載器機(jī)制的實(shí)現(xiàn),每一個(gè)程序模塊(OSGi中稱為Bundle)都有一個(gè)自己的類加載器,當(dāng)需要更換一個(gè)Bundle時(shí),就把Bundle連同類加載器一起換掉以實(shí)現(xiàn)代碼的熱替換。在OSGi環(huán)境下,類加載器不再雙親委派模型推薦的樹狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為更加復(fù)雜的網(wǎng)狀結(jié)構(gòu),當(dāng)收到類加載請(qǐng)求時(shí),OSGi將按照下面的順序進(jìn)行類搜索:

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

      2)否則,將委派列表名單內(nèi)的類,委派給父類加載器加載。

      3)否則,將Import列表中的類,委派給Export這個(gè)類的Bundle的類加載器加載。

      4)否則,查找當(dāng)前Bundle的ClassPath,使用自己的類加載器加載。

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

      6)否則,查找Dynamic Import列表的Bundle,委派給對(duì)應(yīng)Bundle的類加載器加載。

      7)否則,類查找失敗。

      上面的查找順序中只有開頭兩點(diǎn)仍然符合雙親委派模型的原則,其余的類查找都是在平級(jí)的類加載器中進(jìn)行的,關(guān)于OSGi的其他內(nèi)容,筆者就不再展開了。

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

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

  • 引入的版本:

    • 在JDK 9中引入的Java模塊化系統(tǒng)(Java Platform Module System,JPMS)是對(duì)Java技術(shù)的一次重要升級(jí),為了能夠?qū)崿F(xiàn)模塊化的關(guān)鍵目標(biāo)——<font color=red>可配置的封裝隔離機(jī)制</font>,Java虛擬機(jī)對(duì)類加載架構(gòu)也做出了相應(yīng)的變動(dòng)調(diào)整,才使模塊化系統(tǒng)得以順利地運(yùn)作。
  • Java的模塊定義還包含以下內(nèi)容:

    • 依賴其他模塊的列表。

    • 導(dǎo)出的包列表,即其他模塊可以使用的列表。

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

    • 使用的服務(wù)列表。

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

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

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

      • <font color=Fuchsia>JDK9之前因?yàn)榛诼窂郊虞d類,一旦類路徑缺失對(duì)應(yīng)依賴,那就只能等程序發(fā)生該類型的加載、鏈接時(shí)才能報(bào)錯(cuò)</font>

        • <font color=DeepPink>在JDK9之前是基于類路徑(ClassPath)來查找依賴的</font>,<font color=green>如果類路徑中缺失了運(yùn)行時(shí)依賴的類型</font>,那就<font color=red>只能等</font>程序運(yùn)行到發(fā)生<font color=apple green>該類型的加載、鏈接時(shí)才會(huì)報(bào)出運(yùn)行的異常</font>。
      • <font color=Fuchsia>JDK9之后如果啟用模塊化進(jìn)行封裝,模塊可以聲明對(duì)其他模塊的顯示依賴,讓JVM在啟動(dòng)時(shí)驗(yàn)證依賴關(guān)系是否完備,如果缺失則直接報(bào)錯(cuò)</font>

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

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

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

2.1 模塊兼容性

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

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

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

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

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

        • 相應(yīng)地,只要放在模塊路徑上的JAR文件,即使沒有使用JMOD后綴,甚至說其中并不包含module-info.class文件,它也仍然會(huì)被當(dāng)作一個(gè)模塊來對(duì)待。

    • 記憶版:

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

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

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

      • JAR文件在模塊路徑的訪問規(guī)則:如果把一個(gè)傳統(tǒng)的、不包含模塊定義的JAR文件放置到模塊路徑中,它就會(huì)變成一個(gè)自動(dòng)模塊(Automatic Module)。盡管不包含module-info.class,但自動(dòng)模塊將默認(rèn)依賴于整個(gè)模塊路徑中的所有模塊,因此可以訪問到所有模塊導(dǎo)出的包,自動(dòng)模塊也默認(rèn)導(dǎo)出自己所有的包。

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

2.2 模塊化下的類加載器

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

  • 1.擴(kuò)展類加載器(Extension Class Loader)被平臺(tái)類加載器(Platform Class Loader)取代。

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

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

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

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

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

    • 現(xiàn)在啟動(dòng)類加載器、平臺(tái)類加載器、應(yīng)用程序類加載器全都繼承于jdk.internal.loader.BuiltinClassLoader,在BuiltinClassLoader中實(shí)現(xiàn)了新的模塊化架構(gòu)下類如何從模塊中加載的邏輯,以及模塊中資源可訪問性的處理。


            jdk9之前的類加載器繼承架構(gòu)
      
image.png

JDK 9及以后的類加載器繼承架構(gòu)

image.png

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

    • 當(dāng)平臺(tái)及應(yīng)用程序類加載器收到類加載請(qǐng)求,在委派給父加載器加載前,要先判斷該類是否能夠歸屬到某一個(gè)系統(tǒng)模塊中,如果可以找到這樣的歸屬關(guān)系,就要優(yōu)先委派給負(fù)責(zé)那個(gè)模塊的加載器完成加載,也許這可以算是對(duì)雙親委派的第四次破壞。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容