java中的類載入器ClassLoader和類初始化

來源:互聯網
上載者:User

標籤:code   except   常量   div   效率   java   ffffff   class   lis   

 每個類編譯後產生一個Class對象,儲存在.class檔案中,JVM使用類載入器(Class Loader)來載入類的位元組碼檔案(.class),類載入器實質上是一條類載入器鏈,一般的,我們只會用到一個原生的類載入器AppClassLoader,它只載入Java API等可信類,通常只是在本地磁碟中載入,這些類一般就夠我們使用了。如果我們需要從遠程網路或資料庫中下載.class位元組碼檔案,那就需要我們來掛載額外的類載入器。

        一般來說,類載入器是按照樹形的階層組織的,每個載入器都有一個父類載入器。另外,每個類載入器都支援代理模式,即可以自己完成Java類的載入工作,也可以代理給其它類載入器。

ClassLoader中的幾個實作類別

    1、Bootstrap ClassLoader 這個是JVM載入自身工作需要的類,完全由JVM自己來控制,外部無法訪問到這個;

    2、ExtClassLoader比較特殊的,服務的特定目標在System.getProperty("java.ext.dirs");

    3、AppClassLoader,父類是ExtClassLoader,java中參數-classpath中的類都可以被這個類載入器載入;

    4、URLClassLoader,一般這個類幫我們實現了大部分的工作,自訂可以繼承這個類,這樣僅僅在需要的地方做修改就行了;

       類載入器的載入順序有兩種,一種是父類優先策略,一種是是自己優先策略,父類優先策略是比較一般的情況(如JDK採用的就是這種方式),在這種策略下,類在載入某個Java類之前,會嘗試代理給其父類載入器,只有當父類載入器找不到時,才嘗試子類載入器去載入,如果找到了,自己就不用載入。自己優先的策略與父類優先相反,它會首先嘗試自己載入,如果找到了就不用父類載入器去載入,只有找不到的時候才要父類載入器去載入,這種在web容器(如tomcat)中比較常見。

動態載入

       不管使用什麼樣的類載入器,類都是在第一次被用到時,動態載入到JVM的。這句話有兩層含義:

  1. Java程式在運行時並不一定被完整載入,只有當發現該類還沒有載入時,才去本地或遠程尋找類的.class檔案並驗證和載入(賴載入);
  2. 當程式建立了第一個對類的靜態成員的引用(如類的靜態變數、靜態方法、構造方法——構造方法也是靜態)時,才會載入該類。Java的這個特性叫做:動態載入

JVM載入clas檔案到記憶體的方式

    1、顯示載入:不通過代碼裡的ClassLoader調用,而是JVM來自動載入類到記憶體中的方式;

            1.1、通過Class中的forName;

            1.2、通過ClassLoader中的loadClass

            1.3、通過ClasLoader中的findSystemClass

    2、隱藏載入:通過代碼中ClassLoader來載入的方式;

如何載入class檔案

    1)載入(Loading),由類載入器執行,尋找位元組碼,並建立一個Class對象(只是建立);

             a)通過類的全名產生對應類的位元據流。(注意,如果沒找到對應類檔案,只有在類實際使用時才拋出錯誤。)

              b)分析並將這些位元據流轉換為方法區(JVM 的架構:方法區、堆,棧,本地方法棧,pc 寄存器)特定的資料結構(這些資料結構是實現有關的,不同 JVM 有不同實現)。這裡處理了部分檢驗,比如類檔案的魔數的驗證,檢查檔案是否過長或者過短,確定是否有父類(除了 Obecjt 類)。

              c)建立對應類的 java.lang.Class 執行個體(注意,有了對應的 Class 執行個體,並不意味著這個類已經完成了載入鏈連結!)。

     2)連結(Linking),驗證位元組碼,為靜態域分配儲存空間(只是分配,並不初始化該儲存空間),解析該類建立所需要的對其它類的應用;

              a)驗證(verification)

連結的第三部解析會把類中成員方法、成員變數、類和介面的符號引用替換為直接引用,而在這之前,需要檢測被引用的類型正確性和接入屬性是否正確(就是 public ,private 的的問題),諸如檢查 final class 又沒有被繼承,檢查靜態變數的正確性等等。(注意到實際上有一部分驗證過程已經在載入的過程中執行了。)

              b)準備(preparation)

對類的成員變數分配空間。雖然有初始值,但這個時候不會對他們進行初始化(因為這裡不會執行任何 Java 代碼)。具體如下:所有原始類型的值都為 0。如 float: 0f, int: 0, boolean: 0(注意 boolean 底層實現大多使用 int),參考型別則為 null。值得注意的是,JVM 可能會在這個時期給一些有助於程式運行效率提高的資料結構分配空間。比如方發表(類似與 C++中的虛函數表,參見另一篇博文《Java:方法的虛指派和方法表》)。

              c)解析(Resolution)

首先,為類、介面、方法、成員變數的符號引用定位直接引用(如果符號引用先到常量池中尋找符號,再找先應的類型,無疑會耗費更多時間),完成記憶體結構的布局。

然後,這一步是可選的。可以在符號引用第一次被使用時完成,即所謂的延遲解析(late resolution)。但對使用者而言,這一步永遠是延遲解析的,即使運行時會執行 early resolution,但程式不會顯示的在第一次判斷出錯誤時拋出錯誤,而會在對應的類第一次主動使用的時候拋出錯誤!

最後,這一步與之後的類初始化是不衝突的,並非一定要所有的解析結束以後才執行類的初始化。不同的 JVM 實現不同。詳情見另一篇博文《Java 類載入的延遲初始化》。

     3)初始化(Initialization)。

動態載入類:

 

[html] view plaincopyprint?
  1. public class BeanUtilsTest  
  2. {  
  3.     public static void main(String[] args)  
  4.         throws Exception  
  5.     {  
  6.         Class clz = Class.forName("com.ai.redis.A");  
  7.     }  
  8. }  
  9.   
  10. class A  
  11. {  
  12.     public static int VALUE;  
  13.     static  
  14.     {  
  15.         System.out.println("run parent static code.");  
  16.     }  
  17. }  

輸出結果:列印run parent static code.

類.class:

[html] view plaincopyprint?
  1. public class BeanUtilsTest  
  2. {  
  3.     public static void main(String[] args)  
  4.         throws Exception  
  5.     {  
  6.         Class clz1 = A.class;  
  7.     }  
  8. }  
  9.   
  10. class A  
  11. {  
  12.     public static int VALUE;  
  13.     static  
  14.     {  
  15.         System.out.println("run parent static code.");  
  16.     }  
  17. }  

輸出結果:啥也沒有。

通過以上比較,下面這段代碼應該知道列印什麼了吧。

[html] view plaincopyprint?
  1. public class BeanUtilsTest  
  2. {  
  3.     public static void main(String[] args)  
  4.         throws Exception  
  5.     {  
  6.         System.out.println(A.VALUE);  
  7.     }  
  8. }  
  9.   
  10. class A  
  11. {  
  12.     public static final int VALUE = 10;  
  13.     static  
  14.     {  
  15.         System.out.println("run parent static code.");  
  16.     }  
  17. }  


輸出結果:10

有人要問了,為什麼不列印run parent static code.因為VALUE變數是在編譯時間就已經確定的一個常量值跟類.class檔案是一個道理,所以不列印。

註:編譯時間常量必須滿足3個條件:static的,final的,常量。

 

[html] view plaincopyprint?
  1. <pre class="html" name="code">    static int a;  
  2.     final int b;  
  3.     static final int c = Math.abs(10);  
  4.     static final int d;  
  5.     static  
  6.     {  
  7.         d = 5;  
  8.     }  
PS:

為什麼介面不能定義成員變數,而只能定義 final static 變數。

  • 1.介面是不可執行個體化,它的所有元素都不必是執行個體(對象)層面的。static 滿足了這一點。
  • 2.如果介面的變數能被修改,那麼一旦一個子類實現了這個介面,並修改了介面中的非 final 變數,而該子類的子類再次修改這個非 final 的變數後,造成的結果就是雖然實現了相同的介面,但介面中的變數值是不一樣的。

綜上述,static final 更適合於介面。

參考:

1、《通過類字面常量解釋介面常量為什麼只能定義為 static final,類載入過程—Thinking in java》

2、http://blog.csdn.net/biaobiaoqi/article/details/6909141

3、http://www.cnblogs.com/zhguang/p/3154584.html

4、http://iamzhongyong.iteye.com/blog/2091549

java中的類載入器ClassLoader和類初始化

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.