首頁>技術>

1.一道面試題

這是一道經典的面試題:請描述一下Tomcat如何實現自己獨特的類載入機制。

這道題可以引申出很多類載入器的細節問題:

例如:Tomcat為什麼要實現一套與JVM不同的類載入機制?

例如:Tomcat有哪幾種類載入器?

例如:雙親委派模型不滿足Tomcat的要求嗎?

例如:自己定義一個惡意系統類(比如Object類),會對Tomcat造成傷害嗎?

2.類載入器的實戰價值

類載入器是JVM類載入機制的具體實現。

類載入器的第一個實戰用途就是定位異常:當使用一些三方件時,並且部署在某些容器中,執行時環境一旦出現ClassNotFoundException,就需要利用類載入器的知識進行問題定位。類載入器的第二個實戰用途就是安全:java的二進位制天然地容易被反編譯,為了進行二進位制保護,通常會自定義類載入器。3.類載入器的分類

在Java8中,類載入器的邏輯關係如下圖所示:

從提供者看

JVM自帶1個類載入器:BootstrapClassLoader

JDK自帶2個類載入器:ExtClassLoaderAppClassLoader

從關係看

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虛擬機器》-周志明

14
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 38個自動化測試面試題(附精準答案),爆肝2W字