JVM加載class文件的原理機制

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 找不到,那麼才由自己依照自己的搜索路徑搜索類”,注意喔,這句話具有遞歸性 


Java代碼  收藏代碼
  1. /** 
  2.  * @author Jamson Huang 
  3.  * 
  4.  */  
  5. public class TestClass {  
  6.   
  7.     /** 
  8.      * @param args 
  9.      */  
  10.     public static void main(String[] args)  throws Exception{  
  11.         //調用class加載器  
  12.         ClassLoader cl = TestClass.class.getClassLoader();  
  13.         System.out.println(cl);  
  14.         //調用上一層Class加載器  
  15.         ClassLoader clParent = cl.getParent();  
  16.         System.out.println(clParent);  
  17.         //調用根部Class加載器  
  18.         ClassLoader clRoot = clParent.getParent();  
  19.         System.out.println(clRoot);  
  20.           
  21.     }  
  22.   
  23. }  

Result代碼  收藏代碼
  1. Run, Console中出現的log信息如下:  
  2. sun.misc.Launcher$AppClassLoader@7259da  
  3. sun.misc.Launcher$ExtClassLoader@16930e2  
  4. null  

可以看出TestClass是由AppClassLoader加載器加載的 
AppClassLoader的Parent 加載器是 ExtClassLoader 
但是ExtClassLoader的Parent爲 null 是怎麼回事呵,朋友們留意的話,前面有提到Bootstrap Loader是用C++語言寫的,依java的觀點來看,邏輯上並不存在Bootstrap Loader的類實體,所以在java程序代碼裏試圖打印出其內容時,我們就會看到輸出爲null 
【注:以下內容大部分引用java深度歷險】 
弄明白了上面的示例,接下來直接進入類裝載的委託模型實例,寫兩個文件,如下:

Java代碼  收藏代碼
  1. /** 
  2.  * @author Jamson Huang 
  3.  * 
  4.  */  
  5. public class Test1 {  
  6.   
  7.     /** 
  8.      * @param args 
  9.      */  
  10.     public static void main(String[] args)throws Exception {  
  11.         System.out.println(Test1.class.getClassLoader());  
  12.           
  13.         Test2 test2 = new Test2();  
  14.           
  15.         test2.print();  
  16.     }  
  17.   
  18. }  
  19. /** 
  20.  * @author Jamson Huang 
  21.  * 
  22.  */  
  23. public class Test2 {  
  24.     public void print(){  
  25.         System.out.println(Test2.class);  
  26.         System.out.println(this.getClass());  
  27.         System.out.println(Test2.class.getClassLoader());  
  28.     }  
  29. }  
Result代碼  收藏代碼
  1. Run,Console出現log如下:  
  2. sun.misc.Launcher$AppClassLoader@7259da  
  3. class com.java.test.Test2  
  4. class com.java.test.Test2  
  5. sun.misc.Launcher$AppClassLoader@7259da  
    7. 預先加載與依需求加載 

    Java 運行環境爲了優化系統,提高程序的執行速度,在 JRE 運行的開始會將 Java 運行所需要的基本類採用預先加載( pre-loading )的方法全部加載要內存當中,因爲這些單元在 Java 程序運行的過程當中經常要使用的,主要包括 JRE 的 rt.jar 文件裏面所有的 .class 文件。 

    當 java.exe 虛擬機開始運行以後,它會找到安裝在機器上的 JRE 環境,然後把控制權交給 JRE , JRE 的類加載器會將 lib 目錄下的 rt.jar 基礎類別文件庫加載進內存,這些文件是 Java 程序執行所必須的,所以系統在開始就將這些文件加載,避免以後的多次 IO 操作,從而提高程序執行效率。 

    圖( 2 )我們可以看到多個基礎類被加載, java.lang.Object,java.io.Serializable 等等。
    相對於預先加載,我們在程序中需要使用自己定義的類的時候就要使用依需求加載方法( load-on-demand ),就是在 Java 程序需要用到的時候再加載,以減少內存的消耗,因爲 Java 語言的設計初衷就是面向嵌入式領域的。
    8. 自定義類加載機制 

    之前我們都是調用系統的類加載器來實現加載的,其實我們是可以自己定義類加載器的。利用 Java 提供的 java.net.URLClassLoader 類就可以實現。下面我們看一段範例: 

    Java代碼  收藏代碼
    1. try{   
    2. URL url = new URL("file:/d:/test/lib/");   
    3. URLClassLoader urlCL = new URLClassLoader(new URL[]{url});   
    4. Class c = urlCL.loadClass("TestClassA");   
    5. TestClassA object = (TestClassA)c.newInstance();   
    6. object.method();   
    7. }catch(Exception e){   
    8. e.printStackTrace();   
    9. }   


    我們通過自定義的類加載器實現了 TestClassA 類的加載並調用 method ()方法。分析一下這個程序:首先定義 URL 指定類加載器從何處加載類, URL 可以指向網際網絡上的任何位置,也可以指向我們計算機裏的文件系統 ( 包含 JAR 文件 ) 。上述範例當中我們從 file:/d:/test/lib/ 處尋找類;然後定義 URLClassLoader 來加載所需的類,最後即可使用該實例了。 

    9. 類加載器的階層體系 

    討論了這麼多以後,接下來我們仔細研究一下 Java 的類加載器的工作原理: 

    當執行 java ***.class 的時候, java.exe 會幫助我們找到 JRE ,接着找到位於 JRE 內部的 jvm.dll ,這纔是真正的 Java 虛擬機器 , 最後加載動態庫,激活 Java 虛擬機器。虛擬機器激活以後,會先做一些初始化的動作,比如說讀取系統參數等。一旦初始化動作完成之後,就會產生第一個類加載器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰寫而成,這個 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化動作之外,最重要的就是加載 Launcher.java 之中的 ExtClassLoader ,並設定其 Parent 爲 null ,代表其父加載器爲 BootstrapLoader 。然後 Bootstrap Loader 再要求加載 Launcher.java 之中的 AppClassLoader ,並設定其 Parent 爲之前產生的 ExtClassLoader 實體。這兩個加載器都是以靜態類的形式存在的。這裏要請大家注意的是, Launcher$ExtClassLoader.class 與 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加載,所以 Parent 和由哪個類加載器加載沒有關係。 

    下面的圖形可以表示三者之間的關係: 
    BootstrapLoader <---(Extends)----AppClassLoader <---(Extends)----ExtClassLoader 

    這三個加載器就構成我們的 Java 類加載體系。他們分別從以下的路徑尋找程序所需要的類: 

    BootstrapLoader : sun.boot.class.path 
    ExtClassLoader: java.ext.dirs 
    AppClassLoader: java.class.path 

    這三個系統參量可以通過 System.getProperty() 函數得到具體對應的路徑。大家可以自己編程實現查看具體的路徑。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章