轉載請標明出處:http://blog.csdn.net/xuefeng0707/article/details/9132339
類載入的全過程分為五個階段:載入、驗證、準備、解析、初始化。
1. 載入
這個階段可分為三段:
(1)載入二進位位元組流
根據類的全限定名(包名+類名),擷取此類的二進位位元組流。
虛擬機器規範沒有指定二進位位元組流從哪裡讀取,可以是class檔案,可以是jar,也可以由動態代理在運行時產生,等等,只要是符合規範的位元組流即可,由類載入器來決定位元組流的來源。
(2)產生方法區的資料結構
根據前一步讀取到的位元組,在方法區建立運行時資料結構。
(3)建立Class執行個體
在Java堆中建立java.lang.Class的執行個體,作為程式碼訪問類型資料的外部介面。
2. 驗證
這個階段驗證讀取到的二進位位元組流是否符合虛擬機器規範中Class檔案的儲存格式,如果不符合,拋出java.lang.VerifyError異常或其子類的異常。
這個階段分為四段:
(1)檔案格式驗證
驗證位元組流是否符合Class檔案格式的規範。
有以下這些驗證點:
- 是否已魔數0xCAFEBABE開頭。
- 主次版本號碼是否能被當前虛擬機器處理。
- 常量池的常量中是否有不被支援的類型。
- 常量池裡的項是否執行不存在的常量或不符合類型的常量。
- 。。。。。。
註:這裡的常量池不是指運行時虛擬機器記憶體中的常量池,而是class檔案的一部分。 (http://www.blogjava.net/DLevin/archive/2011/09/05/358033.html)經過檔案格式驗證後,位元組流進入虛擬機器記憶體的方法區進行儲存,具體的資料結構實現由虛擬機器決定。後面的驗證都是基於此處的方法區中的資料結構進行的。(2)中繼資料驗證進行語義分析,以確保class檔案描述的類資訊符合Java語言的規範。也就是編譯能通過。有以下這些驗證點:
- 是否 有父類(除java.lang.Object外,所有類都應有父類)。
- 是否繼承了final類。
- 如果不是抽象類別,是否實現了所有需要實現的方法。
- 。。。。。。
(3)位元組碼驗證進行資料流和控制流程分析,對類的方法體進行校正,確保類的方法在運行時不作出危害虛擬機器安全的行為。
- 確保 跳轉指令不會跳到方法體以外的指令上。
- 確保類型轉換是合法有效。
- 。。。。。。
(4)符號引用驗證在“解析”階段,把常量池內的符號引用轉化為直接飲用的時候,要先驗證符號引用。
- 通過全限定名,是否能找到相應的類
- 是否能找到指定類中的指定的欄位或方法
- 類、欄位或方法的可訪問性是否合適。
- 。。。。。。
如果無法通過符號引用驗證,將拋出一個java.lang.IncompatibleClassChangeError的子類。
3. 準備在方法區,為static field分配記憶體,並設定其初始值。這裡設定的初始值,是Java虛擬機器定義的對應於各種類型的預設值。例如:
private static int value=123;
經過準備階段後,value的值為0,而不是123。賦值是在初始化階段的<clinit>()完成的。
但是,對於static final field,在此階段是直接賦值的。例如:
private static final int value=123;
經過準備階段後,value的值為123。
Java虛擬機器中各種類型的預設初始值。
| 資料類型 |
預設初始值 |
| int |
0 |
| long |
0L |
| short |
(short)0 |
| char |
'\u0000' |
| byte |
(byte)0 |
| boolean |
false |
| float |
0.0f |
| double |
0.0d |
| reference |
null |
4. 解析
把類檔案的常量池部分的符號引用轉化為運行時常量池的直接引用。
5. 初始化
執行<clinit>()。
- <clinit>()中包含了一個類中所有類變數的賦值動作和靜態塊(寫在static{ }裡)的語句。執行順序按在源檔案中出現的順序。
- <clinit>()不需要調用父類的<clinit>(),這是由虛擬機器來確保完成的。
- 介面的<clinit>()中只包含介面中欄位的賦值操作,父介面的<clinit>()不需要在子介面或實作類別的<clinit>()之前執行,只有在真正引用到了父介面中定義的欄位時,才會執行父介面的<clinit>()。
- <clinit>()是安全執行緒的。如果多個線程同時去初始化一個類,只有一個線程會去執行初始化,其他的線程都會阻塞。