天天看點

JVM加載class檔案的原理機制

1.Java中的所有類,必須被裝載到jvm中才能運作,這個裝載工作是由jvm中的類裝載器完成的, 

類裝載器所做的工作實質是把類檔案從硬碟讀取到記憶體中

2.java中的類大緻分為三種: 

1.系統類 

    2.擴充類 

3.由程式員自定義的類

3.類裝載方式,有兩種 

    1.隐式裝載, 程式在運作過程中當碰到通過new

等方式生成對象時,隐式調用類裝載器加載對應的類到jvm中, 

    2.顯式裝載,

通過class.forname()等方法,顯式加載需要的類 

隐式加載與顯式加載的差別: 

    兩者本質是一樣?, 

4.類加載的動态性展現 

一個應用程式總是由n多個類組成,Java程式啟動時,并不是一次把所有的類全部加載後再 

運作,它總是先把保證程式運作的基礎類一次性加載到jvm中,其它類等到jvm用到的時候再加載,這樣的好處是節省了記憶體的開銷,因為java最早就是為嵌入式系統而設計的,記憶體寶貴,這是一種可以了解的機制,而用到時再加載這也是java動态性的一種展現

5.java類裝載器 

Java中的類裝載器實質上也是類,功能是把類載入jvm中,值得注意的是jvm的類裝載器并不是一個,而是三個,層次結構如下: 

Bootstrap Loader  -

負責加載系統類 

          - -

ExtClassLoader  -

負責加載擴充類 

- - AppClassLoader  -

負責加載應用類 

為什麼要有三個類加載器,一方面是分工,各自負責各自的區塊,另一方面為了實作委托模型,下面會談到該模型

6. 類加載器之間是如何協調工作的 

前面說了,java中有三個類加載器,問題就來了,碰到一個類需要加載時,它們之間是如何協調工作的,即java是如何區分一個類該由哪個類加載器來完成呢。 

在這裡java采用了委托模型機制,這個機制簡單來講,就是“類裝載器有載入類的需求時,會先請示其Parent使用其搜尋路徑幫忙載入,如果Parent

找不到,那麼才由自己依照自己的搜尋路徑搜尋類”,注意喔,這句話具有遞歸性 

下面舉一個例子來說明,為了更好的了解,先弄清楚幾行代碼: 

Public

class Test{ 

    Public static void main(String[]

arg){ 

      ClassLoader c  =

Test.class.getClassLoader(); 

//擷取Test類的類加載器 

System.out.println(c);  

      ClassLoader

c1 = c.getParent(); 

//擷取c這個類加載器的父類加載器 

System.out.println(c1); 

      ClassLoader c2 =

c1.getParent();//擷取c1這個類加載器的父類加載器 

System.out.println(c2); 

  } 

把以上代碼存到d:\my

檔案夾下,直接編譯,然後在dos模式下運作 

D:\my\java Test 

。。。AppClassLoader。。。 

。。。ExtClassLoader。。。 

    Null

D:\my

注:

。。。表示省略了内容 

可以看出Test是由AppClassLoader加載器加載的 

AppClassLoader的Parent

加載器是 ExtClassLoader

但是ExtClassLoader的Parent為 null 是怎麼回事呵,朋友們留意的話,前面有提到Bootstrap

Loader是用C++語言寫的,依java的觀點來看,邏輯上并不存在Bootstrap

Loader的類實體,是以在java程式代碼裡試圖列印出其内容時,我們就會看到輸出為null 

【注:以下内容大部分引用java深度曆險】 

弄明白了上面的示例,接下來直接進入類裝載的委托模型執行個體,寫兩個檔案,如下: 

檔案:Test1.java 

class Test1{ 

System.out.println(Test1.class.getClassLoader()); 

Test2 t2 = new Test2(); 

T2.print(); 

}

檔案: Test2.java 

Public class Test2{ 

Public void prin(){ 

System.out.println(this.getClass().getClassLoader()); 

這兩個類的作用就是列印出載入它們的類裝載器是誰, 将這兩個檔案儲存到d:\my目錄下,編譯後,我們在複制兩份,分别置于

<JRE所在目錄>\classes下(注意,剛開始我們的系統下沒有此目錄,需自己建立) 與

<JRE所在目錄>\lib\ext\classes下(同樣注意,開始我們的系統下也沒此目錄,手工建立),

然後切換到d:\my目錄下開始測試,

測試一: 

<JRE所在目錄>\classes下 

Test1.class 

Test2.class

<JRE所在目錄>\lib\ext\classes下 

D:\my下 

dos下輸入運作指令,結果如下: 

D:\my>java

Test1 

Null 

Null

D:\my> 

從輸出結果我們可以看出,當AppClassLoader要載入Test1.class時,先請其Parent,也就是ExtClassLoader來載

入,而ExtclassLoader又請求其Parent,即Bootstrap Loader來載入Test1.class. 由于

<JRE所在目錄>\Classes目錄為Bootstrap Loader的搜尋路徑之一,是以Bootstrap

Loader找到了Test1.class,是以将它載入,接着在Test1.class之内有載入Test2.class的需求,由于

Test1.class是由Bootstrap Loader所載入,是以Test2.class内定是由Bootstrap

Loader根據其搜尋路徑來找,因Test2.class也位于Bootstrap

Loader可以找到的路徑下,是以也被載入了,最後我們看到Test1.class與Test2.class都是由Bootstrap

Loader(null)載入。

測試二: 

Test1.class

Exception in thread “main”

java.lang.NoClassdefFoundError:Test2 at Test1.main。。。 

D:\my>

Loader根據其搜尋路徑來找,但是因為Bootstrap Loader根本找不到Test2.class(被我們删除了),而Bootstrap

Loader又沒有Parent,是以無法載入Test2.class.最後我們看到Test1.class是由Bootstrap

Loader(null)載入,而Test2.class則無法載入

測試三 

<JRE所在目錄>\classes下

入,而ExtclassLoader又請求其Parent,即Bootstrap Loader來載入Test1.class.但是Bootstrap

Loader無法在其搜尋路徑下找到Test1.class(被我們删掉了),是以ExtClassLoader隻得自己搜尋,是以

ExtClassLoader在其搜尋路徑

<JRE所在目錄>\lib\ext\classes下找到了Test1.class,是以将它載入,接着在Test1.class之内有載

入Test2.class的需求,由于Test1.class是由ExtClassLoader所載入,是以Test2.class内定是由

ExtClassLoader根據其搜尋路徑來找,但是因為ExtClassLoader有Parent,是以先由Bootstrap

Loader幫忙尋找,Test2.class位于Bootstrap Loader可以找到的路徑下,是以被Bootstrap

Loader載入了.最後我們看到Test1.class是由ExtClassLoader載入,而Test2.class則是由Bootstrap

Loader(null)載入

    了解了以上規則,請朋友們自行分析以下場景的執行結果

測試四: 

測試五: 

測試六: 

<JRE所在目錄>\lib\ext\classes下

測試七: 

以上了解,錯漏之處請朋友們及時指出,以免怠誤大家