1.一道面試題
這是一道經典的面試題:請描述一下Tomcat如何實現自己獨特的類載入機制。
這道題可以引申出很多類載入器的細節問題:
例如:Tomcat為什麼要實現一套與JVM不同的類載入機制?
例如:Tomcat有哪幾種類載入器?
例如:雙親委派模型不滿足Tomcat的要求嗎?
例如:自己定義一個惡意系統類(比如Object類),會對Tomcat造成傷害嗎?
2.類載入器的實戰價值類載入器是JVM類載入機制的具體實現。
類載入器的第一個實戰用途就是定位異常:當使用一些三方件時,並且部署在某些容器中,執行時環境一旦出現ClassNotFoundException,就需要利用類載入器的知識進行問題定位。類載入器的第二個實戰用途就是安全:java的二進位制天然地容易被反編譯,為了進行二進位制保護,通常會自定義類載入器。3.類載入器的分類在Java8中,類載入器的邏輯關係如下圖所示:
從提供者看JVM自帶1個類載入器:BootstrapClassLoader
JDK自帶2個類載入器:ExtClassLoader、AppClassLoader
從關係看BootstrapClassLoader:它會建立ExtClassLoader和AppClassLoader,它是ExtClassLoader的父級載入器。
ExtClassLoader是AppClassLoader的父級載入器。
從實現看BootstrapClassLoader:用C++實現
ExtClassLoader:用Java實現,繼承自ClassLoader
AppClassLoader:用Java實現,繼承自ClassLoader
從職責看BootstrapClassLoader:載入(JAVA_HOME/jre/lib/*.jar或sun.boot.class.path下所有內容,用於提供JVM自身需要的類。
ExtClassLoader:載入屬性java.ext.dirs所指定的目錄中的類庫,或從JDK的安裝目錄jre/lib/ext/*.jar。
AppClassLoader:載入環境變數classpath中的jar或屬性java.class.path指定路徑下的類庫。
限制BootstrapClassLoader:為了安全,只加載包名為java、javax、sun開頭的類檔案
JVM自帶了上述3種類載入器,就不得不面臨兩個問題:
1.載入流程問題:有一個類A,哪個類載入器負責去載入?
2.唯一性問題:有一個類A,不同的類載入器能同時載入它嗎?
4.相對唯一類載入機制這樣定義類的唯一性,必須滿足如下兩個條件:
第一、載入到JVM中的類模板本身是相同的第二、載入這個類的載入器是同一個第一個條件比較好理解,這個被載入的類,來自同一個class檔案、類的全限定名相同。
第二個條件則體現了哲學定義中的"唯一"——在一定約束下,事物具備獨一無二的性質。
反過來理解第二個條件,我們可以得到如下推論:
推論1:兩個類載入器可以載入同一個類推論2:如果發生了推論1,則此時JVM中載入完成的2個類模板被JVM認為是2個不同的類模板。5.雙親委派明白了"相對唯一性",JVM還是要講一點"江湖規矩"——在沒有某些特殊訴求的情況下,還是要約束一下類載入器們不能太渣,不要反覆載入同一個類模板。
JVM定義了這麼一套載入規則:
類載入器存在上下級關係:Bootstrap是一把手,Ext是二把手,App是小弟。類載入器存在先後順序:一把手(Bootstrap)初始化的時候,將二把手(Ext)、小弟(App)給創建出來。類載入器存在載入詢問機制:當小弟(App)準備載入1個類模板,會先去求助一下二把手(Ext)載入過這個類嗎、能不能幫我載入一下?二把手(Ext)收到小弟(App)的求助,也會馬上去求助一把手(Bootstrap)。一把手(Bootstrap)沒有更上級可以求助,於是看看自己能否載入,如果不能,就把權力下放給二把手(Ext)。二把手(Ext)一樣的處理邏輯,如果不能載入,就把權力下放給小弟(App)。繞了這麼一大圈,小弟(App)還得自己載入。這套載入規則,就是雙親委派模型,Parents Delegation Model,也被稱為溯源委派載入模型。
為啥JVM為這種載入規則取了這麼奇怪的名字?
筆者聽到一個段子覺得比較貼切——類載入的相對唯一性就是"渣男邏輯",而雙親委派模型就是"媽寶模型":
小明(App)的家庭作業不會做,就叫媽媽(Ext)幫忙做。
媽媽(Ext)有點忙就叫奶奶(Bootstrap)做。
奶奶(Bootstrap)說我現在不舒服,還是媽媽(Ext)做吧。
媽媽(Ext)說我現在忙著做飯,小明(App)你自己來吧。
小明(App)看繞了一圈,還得自己做作業。
理解清楚這個"雙親"的含義,我們就知道在很多講JVM原理的文章中,提到的父級載入器、父類載入器,不是表示這三種類載入器是父子關係,而是上下級關係。
6.測試我們用幾個測試用例,驗證一下:
6.1.驗證三種類載入器的上下級關係程式碼:先獲得AppClassLoader,然後呼叫getParent(),獲得上一級ClassLoader
執行結果:說明AppClassLoader的上級是ExtClassLoader,ExtClassLoader的上級是Bootrap(C++實現,所以列印為null)
6.2.驗證BootstrapClassLoader已經載入的類程式碼透過getBootstrapClassPath,獲得Bootstrap載入器已經載入的類
再找到Bootstrap載入器已經載入的某一個類對應的類載入器,反過來驗證它的類載入器是否是Bootstrap。
執行結果Bootstrap的職責是載入JVM自身需要的類,目錄如下圖:
6.3.驗證ExtClassLoader已經載入的類程式碼獲得java.ext.dirs對應的檔案路徑,再選擇該路徑下的類,檢視類載入器型別
執行結果ExtClassLoader的職責是載入java.ext.dirs下的類
6.4.嘗試破壞java.lang包程式碼自建一個java.lang.String類
執行結果類載入器載入的時候,認為這是一個被禁止的包路徑,防禦了我們偽造java.lang包下的基礎類
7.總結類載入器是類載入機制的具體實現,本文講解了如下知識點:
類載入器的分類類載入器的相對唯一性雙親委派模型透過程式碼,驗證了:類載入器之間的上下級關係Bootstrap、Ext類載入器的職責JVM如何防禦我們對核心庫的破壞行為8.參考《深入理解Java虛擬機器》-周志明