<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                Java 通過引入字節碼和 JVM 機制,提供了強大的跨平臺能力,理解 Java 的類加載機制是深入 Java 開發的必要條件,也是個面試考察熱點。 今天我要問你的問題是,請介紹類加載過程,什么是雙親委派模型? ## 典型回答 一般來說,我們把 Java 的類加載過程分為三個主要步驟:加載、鏈接、初始化,具體行為在[Java 虛擬機規范](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html)里有非常詳細的定義。 首先是加載階段(Loading),它是 Java 將字節碼數據從不同的數據源讀取到 JVM 中,并映射為 JVM 認可的數據結構(Class 對象),這里的數據源可能是各種各樣的形態,如 jar 文件、class 文件,甚至是網絡數據源等;如果輸入數據不是 ClassFile 的結構,則會拋出 ClassFormatError。 加載階段是用戶參與的階段,我們可以自定義類加載器,去實現自己的類加載過程。 第二階段是鏈接(Linking),這是核心的步驟,簡單說是把原始的類定義信息平滑地轉化入 JVM 運行的過程中。這里可進一步細分為三個步驟: * 驗證(Verification),這是虛擬機安全的重要保障,JVM 需要核驗字節信息是符合 Java 虛擬機規范的,否則就被認為是 VerifyError,這樣就防止了惡意信息或者不合規的信息危害 JVM 的運行,驗證階段有可能觸發更多 class 的加載。 * 準備(Preparation),創建類或接口中的靜態變量,并初始化靜態變量的初始值。但這里的“初始化”和下面的顯式初始化階段是有區別的,側重點在于分配所需要的內存空間,不會去執行更進一步的 JVM 指令。 * 解析(Resolution),在這一步會將常量池中的符號引用(symbolic reference)替換為直接引用。在[Java 虛擬機規范](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3)中,詳細介紹了類、接口、方法和字段等各個方面的解析。 最后是初始化階段(initialization),這一步真正去執行類初始化的代碼邏輯,包括靜態字段賦值的動作,以及執行類定義中的靜態初始化塊內的邏輯,編譯器在編譯階段就會把這部分邏輯整理好,父類型的初始化邏輯優先于當前類型的邏輯。 再來談談雙親委派模型,簡單說就是當類加載器(Class-Loader)試圖加載某個類型的時候,除非父加載器找不到相應類型,否則盡量將這個任務代理給當前加載器的父加載器去做。使用委派模型的目的是避免重復加載 Java 類型。 ## 考點分析 今天的問題是關于 JVM 類加載方面的基礎問題,我前面給出的回答參考了 Java 虛擬機規范中的主要條款。如果你在面試中回答這個問題,在這個基礎上還可以舉例說明。 我們來看一個經典的延伸問題,準備階段談到靜態變量,那么對于常量和不同靜態變量有什么區別? 需要明確的是,沒有人能夠精確的理解和記憶所有信息,如果碰到這種問題,有直接答案當然最好;沒有的話,就說說自己的思路。 我們定義下面這樣的類型,分別提供了普通靜態變量、靜態常量,常量又考慮到原始類型和引用類型可能有區別。 ~~~ public class CLPreparation { public static int a = 100; public static final int INT_CONSTANT = 1000; public static final Integer INTEGER_CONSTANT = Integer.valueOf(10000); } ~~~ 編譯并反編譯一下: ~~~ Javac CLPreparation.java Javap –v CLPreparation.class ~~~ 可以在字節碼中看到這樣的額外初始化邏輯: ~~~ 0: bipush 100 2: putstatic #2 // Field a:I 5: sipush 10000 8: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 11: putstatic #4 // Field INTEGER_CONSTANT:Ljava/lang/Integer; ~~~ 這能讓我們更清楚,普通原始類型靜態變量和引用類型(即使是常量),是需要額外調用 putstatic 等 JVM 指令的,這些是在顯式初始化階段執行,而不是準備階段調用;而原始類型常量,則不需要這樣的步驟。 關于類加載過程的更多細節,有非常多的優秀資料進行介紹,你可以參考大名鼎鼎的《深入理解 Java 虛擬機》,一本非常好的入門書籍。我的建議是不要僅看教程,最好能夠想出代碼實例去驗證自己對某個方面的理解和判斷,這樣不僅能加深理解,還能夠在未來的應用開發中使用到。 其實,類加載機制的范圍實在太大,我從開發和部署的不同角度,各選取了一個典型擴展問題供你參考: * 如果要真正理解雙親委派模型,需要理解 Java 中類加載器的架構和職責,至少要懂具體有哪些內建的類加載器,這些是我上面的回答里沒有提到的;以及如何自定義類加載器? * 從應用角度,解決某些類加載問題,例如我的 Java 程序啟動較慢,有沒有辦法盡量減小 Java 類加載的開銷? 另外,需要注意的是,在 Java 9 中,Jigsaw 項目為 Java 提供了原生的模塊化支持,內建的類加載器結構和機制發生了明顯變化。我會對此進行講解,希望能夠避免一些未來升級中可能發生的問題。 ## 知識擴展 首先,從架構角度,一起來看看 Java 8 以前各種類加載器的結構,下面是三種 Oracle JDK 內建的類加載器。 * 啟動類加載器(Bootstrap Class-Loader),加載 jre/lib 下面的 jar 文件,如 rt.jar。它是個超級公民,即使是在開啟了 Security Manager 的時候,JDK 仍賦予了它加載的程序 AllPermission。 對于做底層開發的工程師,有的時候可能不得不去試圖修改 JDK 的基礎代碼,也就是通常意義上的核心類庫,我們可以使用下面的命令行參數。 ~~~ # 指定新的 bootclasspath,替換 java.* 包的內部實現 java -Xbootclasspath:<your_boot_classpath> your_App # a 意味著 append,將指定目錄添加到 bootclasspath 后面 java -Xbootclasspath/a:<your_dir> your_App # p 意味著 prepend,將指定目錄添加到 bootclasspath 前面 java -Xbootclasspath/p:<your_dir> your_App ~~~ 用法其實很易懂,例如,使用最常見的 “/p”,既然是前置,就有機會替換個別基礎類的實現。 我們一般可以使用下面方法獲取父加載器,但是在通常的 JDK/JRE 實現中,擴展類加載器 getParent() 都只能返回 null。 ~~~ public final ClassLoader getParent() ~~~ * 擴展類加載器(Extension or Ext Class-Loader),負責加載我們放到 jre/lib/ext/ 目錄下面的 jar 包,這就是所謂的 extension 機制。該目錄也可以通過設置 “java.ext.dirs”來覆蓋。 ~~~ java -Djava.ext.dirs=your_ext_dir HelloWorld ~~~ * 應用類加載器(Application or App Class-Loader),就是加載我們最熟悉的 classpath 的內容。這里有一個容易混淆的概念,系統(System)類加載器,通常來說,其默認就是 JDK 內建的應用類加載器,但是它同樣是可能修改的,比如: ~~~ java -Djava.system.class.loader=com.yourcorp.YourClassLoader HelloWorld ~~~ 如果我們指定了這個參數,JDK 內建的應用類加載器就會成為定制加載器的父親,這種方式通常用在類似需要改變雙親委派模式的場景。 具體請參考下圖: ![](https://img.kancloud.cn/35/a3/35a3bc241d779ddcc357639547917ca1_779x642.png) 至于前面被問到的雙親委派模型,參考這個結構圖更容易理解。試想,如果不同類加載器都自己加載需要的某個類型,那么就會出現多次重復加載,完全是種浪費。 通常類加載機制有三個基本特征: * 雙親委派模型。但不是所有類加載都遵守這個模型,有的時候,啟動類加載器所加載的類型,是可能要加載用戶代碼的,比如 JDK 內部的 ServiceProvider/[ServiceLoader](https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html)機制,用戶可以在標準 API 框架上,提供自己的實現,JDK 也需要提供些默認的參考實現。 例如,Java 中 JNDI、JDBC、文件系統、Cipher 等很多方面,都是利用的這種機制,這種情況就不會用雙親委派模型去加載,而是利用所謂的上下文加載器。 * 可見性,子類加載器可以訪問父加載器加載的類型,但是反過來是不允許的,不然,因為缺少必要的隔離,我們就沒有辦法利用類加載器去實現容器的邏輯。 * 單一性,由于父加載器的類型對于子加載器是可見的,所以父加載器中加載過的類型,就不會在子加載器中重復加載。但是注意,類加載器“鄰居”間,同一類型仍然可以被加載多次,因為互相并不可見。 在 JDK 9 中,由于 Jigsaw 項目引入了 Java 平臺模塊化系統(JPMS),Java SE 的源代碼被劃分為一系列模塊。 ![](https://img.kancloud.cn/15/13/15138305829ed15f45dd53ec38bd8379_1600x404.png) 類加載器,類文件容器等都發生了非常大的變化,我這里總結一下: * 前面提到的 -Xbootclasspath 參數不可用了。API 已經被劃分到具體的模塊,所以上文中,利用“-Xbootclasspath/p”替換某個 Java 核心類型代碼,實際上變成了對相應的模塊進行的修補,可以采用下面的解決方案: 首先,確認要修改的類文件已經編譯好,并按照對應模塊(假設是 java.base)結構存放, 然后,給模塊打補丁: ~~~ java --patch-module java.base=your_patch yourApp ~~~ * 擴展類加載器被重命名為平臺類加載器(Platform Class-Loader),而且 extension 機制則被移除。也就意味著,如果我們指定 java.ext.dirs 環境變量,或者 lib/ext 目錄存在,JVM 將直接返回**錯誤**!建議解決辦法就是將其放入 classpath 里。 * 部分不需要 AllPermission 的 Java 基礎模塊,被降級到平臺類加載器中,相應的權限也被更精細粒度地限制起來。 * rt.jar 和 tools.jar 同樣是被移除了!JDK 的核心類庫以及相關資源,被存儲在 jimage 文件中,并通過新的 JRT 文件系統訪問,而不是原有的 JAR 文件系統。雖然看起來很驚人,但幸好對于大部分軟件的兼容性影響,其實是有限的,更直接地影響是 IDE 等軟件,通常只要升級到新版本就可以了。 * 增加了 Layer 的抽象, JVM 啟動默認創建 BootLayer,開發者也可以自己去定義和實例化 Layer,可以更加方便的實現類似容器一般的邏輯抽象。 結合了 Layer,目前的 JVM 內部結構就變成了下面的層次,內建類加載器都在 BootLayer 中,其他 Layer 內部有自定義的類加載器,不同版本模塊可以同時工作在不同的 Layer。 ![](https://img.kancloud.cn/20/a6/20a6a22ae11c1be3e08c6fa0bc8a8c00_858x587.png) 談到類加載器,繞不過的一個話題是自定義類加載器,常見的場景有: * 實現類似進程內隔離,類加載器實際上用作不同的命名空間,以提供類似容器、模塊化的效果。例如,兩個模塊依賴于某個類庫的不同版本,如果分別被不同的容器加載,就可以互不干擾。這個方面的集大成者是[Java EE](http://www.oracle.com/technetwork/java/javaee/overview/index.html)和[OSGI](https://en.wikipedia.org/wiki/OSGi)、[JPMS](https://en.wikipedia.org/wiki/Java_Platform_Module_System)等框架。 * 應用需要從不同的數據源獲取類定義信息,例如網絡數據源,而不是本地文件系統。 * 或者是需要自己操縱字節碼,動態修改或者生成類型。 我們可以總體上簡單理解自定義類加載過程: * 通過指定名稱,找到其二進制實現,這里往往就是自定義類加載器會“定制”的部分,例如,在特定數據源根據名字獲取字節碼,或者修改或生成字節碼。 * 然后,創建 Class 對象,并完成類加載過程。二進制信息到 Class 對象的轉換,通常就依賴[defineClass](https://docs.oracle.com/javase/9/docs/api/java/lang/ClassLoader.html#defineClass-java.lang.String-byte:A-int-int-),我們無需自己實現,它是 final 方法。有了 Class 對象,后續完成加載過程就順理成章了。 具體實現我建議參考這個[用例](http://www.baeldung.com/java-classloaders)。 我在[專欄第 1 講](http://time.geekbang.org/column/article/6845)中,就提到了由于字節碼是平臺無關抽象,而不是機器碼,所以 Java 需要類加載和解釋、編譯,這些都導致 Java 啟動變慢。談了這么多類加載,有沒有什么通用辦法,不需要代碼和其他工作量,就可以降低類加載的開銷呢? 這個,可以有。 * 在第 1 講中提到的 AOT,相當于直接編譯成機器碼,降低的其實主要是解釋和編譯開銷。但是其目前還是個試驗特性,支持的平臺也有限,比如,JDK 9 僅支持 Linux x64,所以局限性太大,先暫且不談。 * 還有就是較少人知道的 AppCDS(Application Class-Data Sharing),CDS 在 Java 5 中被引進,但僅限于 Bootstrap Class-loader,在 8u40 中實現了 AppCDS,支持其他的類加載器,在目前 2018 年初發布的 JDK 10 中已經開源。 簡單來說,AppCDS 基本原理和工作過程是: 首先,JVM 將類信息加載, 解析成為元數據,并根據是否需要修改,將其分類為 Read-Only 部分和 Read-Write 部分。然后,將這些元數據直接存儲在文件系統中,作為所謂的 Shared Archive。命令很簡單: ~~~ Java -Xshare:dump -XX:+UseAppCDS -XX:SharedArchiveFile=<jsa> \ -XX:SharedClassListFile=<classlist> -XX:SharedArchiveConfigFile=<config_file> ~~~ 第二,在應用程序啟動時,指定歸檔文件,并開啟 AppCDS。 ~~~ Java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=<jsa> yourApp ~~~ 通過上面的命令,JVM 會通過內存映射技術,直接映射到相應的地址空間,免除了類加載、解析等各種開銷。 AppCDS 改善啟動速度非常明顯,傳統的 Java EE 應用,一般可以提高 20%~30% 以上;實驗中使用 Spark KMeans 負載,20 個 slave,可以提高 11% 的啟動速度。 與此同時,降低內存 footprint,因為同一環境的 Java 進程間可以共享部分數據結構。前面談到的兩個實驗,平均可以減少 10% 以上的內存消耗。 當然,也不是沒有局限性,如果恰好大量使用了運行時動態類加載,它的幫助就有限了。 今天我梳理了一下類加載的過程,并針對 Java 新版中類加載機制發生的變化,進行了相對全面的總結,最后介紹了一個改善類加載速度的特性,希望對你有所幫助。 ## 一課一練 關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,談談什么是 Jar Hell 問題?你有遇到過類似情況嗎,如何解決呢?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看