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);

  }

}

運行結果:

    。。。AppClassLoader。。。

    。。。ExtClassLoader。。。

    Null

注: 。。。表示省略了內容

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

AppClassLoader的Parent 加載器是 ExtClassLoader 但是ExtClassLoader的Parent爲 null 是怎麼回事呵,朋友們留意的話,前面有提到Bootstrap Loader是用C++語言寫的,依java的觀點來看,邏輯上並不存在Bootstrap Loader的類實體,所以在java程序代碼裏試圖打印出其內容時,我們就會看到輸出爲null

 

類裝載器ClassLoader(一個抽象類)描述一下JVM加載class文件的原理機制

類裝載器就是尋找類或接口字節碼文件進行解析並構造JVM內部對象表示的組件,在java中類裝載器把一個類裝入JVM,經過以下步驟:

1、裝載:查找和導入Class文件

2、鏈接:其中解析步驟是可以選擇的

(a)檢查:檢查載入的class文件數據的正確性

(b)準備:給類的靜態變量分配存儲空間

(c)解析:將符號引用轉成直接引用

3、初始化:對靜態變量,靜態代碼塊執行初始化工作

類裝載工作由ClassLoder和其子類負責。JVM在運行時會產生三個ClassLoader:根裝載器,ExtClassLoader(擴展類裝載器)和AppClassLoader,其中根裝載器不是ClassLoader的子類,由C++編寫,因此在java中看不到他,負責裝載JRE的核心類庫,如JRE目錄下的rt.jar,charsets.jar等。ExtClassLoader是ClassLoder的子類,負責裝載JRE擴展目錄ext下的jar類包;AppClassLoader負責裝載classpath路徑下的類包,這三個類裝載器存在父子層級關係,即根裝載器是ExtClassLoader的父裝載器,ExtClassLoader是AppClassLoader的父裝載器。默認情況下使用AppClassLoader裝載應用程序的類

Java裝載類使用“全盤負責委託機制”。“全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入;“委託機制”是指先委託父類裝載器尋找目標類,只有在找不到的情況下才從自己的類路徑中查找並裝載目標類。這一點是從安全方面考慮的,試想如果一個人寫了一個惡意的基礎類(如java.lang.String)並加載到JVM將會引起嚴重的後果,但有了全盤負責制,java.lang.String永遠是由根裝載器來裝載,避免以上情況發生

除了JVM默認的三個ClassLoder以外,第三方可以編寫自己的類裝載器,以實現一些特殊的需求。類文件被裝載解析後,在JVM中都有一個對應的java.lang.Class對象,提供了類結構信息的描述。數組,枚舉及基本數據類型,甚至void都擁有對應的Class對象。Class類沒有public的構造方法,Class對象是在裝載類時由JVM通過調用類裝載器中的defineClass()方法自動構造的

 

ClassLoader重要方法:

(1)public Class<?> loadClass(String name)

                   throws ClassNotFoundException

name參數指定類裝載器需要裝載類的名字,必須使用全限定類名。該方法有一個重載方法loadClass(String name,Boolean resolve),resolve參數告訴類裝載器是否解析該類。在初始化類之前應考慮進行類解析的工作,但並不是所有類都需要解析,如果JVM只需要知道該類是否存在或找出該類的超類,那麼就不需要進行解析

(2)

 

 

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

弄明白了上面的示例,接下來直接進入類裝載的委託模型實例,寫兩個文件,如下:

文件:Test1.java

Public class Test1{

   Public static void main(String[] arg){

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

       Test2 t2 = new Test2();

       T2.print();

  }

}

 

文件: Test2.java

Public class Test2{

   Public void print(){

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

    }

}

 

這兩個類的作用就是打印出載入它們的類裝載器是誰, 將這兩個文件保存到d:\TestClassLoder目錄下,編譯後,我們在複製兩份,分別置於 <JRE所在目錄>\classes下(沒有此目錄,需自己建立) 與 <JRE所在目錄>\lib\ext\classes下(沒此目錄,手工建立), 然後切換到D:\TestClassLoder目錄下開始測試(查看當前用的jdk版本號,我用的jre所在目錄爲是C:\ProgramFiles\Java\jdk1.6.0\jre)

 

 

測試一:

<JRE所在目錄>\classes下

Test1.class

Test2.class

 

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

Test1.class

Test2.class

 

D:\TestClassLoder下

Test1.class

Test2.class

 

dos下輸入運行命令,結果如下:

D:\TestClassLoder>java Test1

Null

Null

 

D:\TestClassLoder>

   

    從輸出結果我們可以看出,當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)載入。

 

若<JRE所在目錄>\lib\ext\classes下沒有這兩個類文件,結果也一樣都爲null

 

測試二:

<JRE所在目錄>\classes下

Test1.class

 

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

Test1.class

Test2.class

 

D:\TestClassLoder下

Test1.class

Test2.class

 

dos下輸入運行命令,結果如下:

D:\TestClassLoder>java Test1

Null

Exception in thread “main” java.lang.NoClassdefFoundError:Test2 at Test1.main。。。

D:\TestClassLoder>

 

    從輸出結果我們可以看出,當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根據其搜索路徑來找,但是因爲Bootstrap Loader根本找不到Test2.class(被我們刪除了),而Bootstrap Loader又沒有Parent,所以無法載入Test2.class.最後我們看到Test1.class是由Bootstrap Loader(null)載入,而Test2.class則無法載入

 

若<JRE所在目錄>\lib\ext\classes下沒有這兩個類文件,結果也一樣

 

 

測試三

<JRE所在目錄>\classes下

 

Test2.class

 

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

Test1.class

Test2.class

 

D:\TestClassLoder下

Test1.class

Test2.class

 

dos下輸入運行命令,結果如下:

D:\TestClassLoder>java Test1

。。。ExtClassLoader。。。

Null

 

D:\TestClassLoder>

 

    從輸出結果我們可以看出,當AppClassLoader要載入Test1.class時,先請其Parent,也就是ExtClassLoader來載入,而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所在目錄>\classes下

 

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

Test1.class

Test2.class

 

D:\TestClassLoder下

Test1.class

Test2.class

 

 

測試五:

<JRE所在目錄>\classes下

 

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

Test1.class

 

D:\TestClassLoder下

Test1.class

Test2.class

 

 

測試六:

<JRE所在目錄>\classes下

 

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

Test2.class

 

D:\TestClassLoder下

Test1.class

Test2.class

 

 

測試七:

<JRE所在目錄>\classes下

 

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

 

D:\TestClassLoder下

Test1.class

Test2.class

 

答案:

測試四:

。。。ExtClassLoader。。。

。。。ExtClassLoader。。。

測試五:

。。。ExtClassLoader。。。

Exception in thread "main"java.lang.NoClassDefFoundError: Test2

       at Test1.main。。。

測試六:

。。。ExtClassLoader。。。

。。。AppClassLoader。。。

測試七:

。。。AppClassLoader。。。

。。。AppClassLoader。。。

發佈了24 篇原創文章 · 獲贊 1 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章