程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> jvm類加載機制

jvm類加載機制

編輯:JAVA綜合教程

jvm類加載機制


一個類的生命周期如下乳所示,應該注意的是這只是各階段開始的順序,並不是一個階段非要等到上一個階段執行完才可以開始。所以可能出現一個階段正在運行,另一個階段也開始運行的情況。

\

java虛擬機規范並沒有規定什麼時候要加載類,但是嚴格規定了有且僅有以下5種情況必須立即對類進行初始化(而加載、驗證、准備、解析要在這之前完成):
1、使用new關鍵字實例化一個對象的時候、讀取或設置一個類的靜態字段或調用一個方法的時候。這裡特別注意的是被final修飾的常量已在編譯階段存入常量池,因此引用一個類的static final字段並不會加載一個類。
2、使用反射方法調用類的時候,如果類還沒有初始化要先初始化。

3、當調用一個類的 時候如果父類還沒有初始化,則要先初始化其父類。

4、當虛擬機啟動的時候,用戶需要制定一個要執行的主類(包括main方法的類),虛擬機會先初始化這個主類

5、當使用jdk1.7的的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則需要先出發其初始化。

下面具體講類的加載過程:

加載

這一階段虛擬機需要完成以下3件事:

1、通過一個類的全限定名來獲取定義此類的二進制字節流

2、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構

3、在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

驗證

驗證的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。主要包括5個階段:

1、文件格式驗證

驗證字節流是否符合Class文件的格式,並且能為當前版本的虛擬機處理。比如“:

a.是否以魔術0xCAFEBABE開頭

b.主次版本號是否在當前虛擬機處理范圍之內

c.Class文件中的常量池的常量中是否有不被支持的常量類型等

2、元數據驗證

在類的層面上對字節碼描述的信息進行語義分析,比如:

a.這個類是否有父類

b.這個類是否繼承了不允許繼承的類,比如final類

c.如果這個類不是抽象類是否實現了父類或接口所有需要實現的方法

d.類的字段、方法是否與父類產生矛盾

3、字節碼驗證

在類的方法層面,主要通過數據流和控制流的分析,對方法體進行校驗,以確保程序語義是合法的、符合邏輯的。比如:保證任意時刻操作數的數據類型與指令代碼序列都能配合工作,比如不會出現棧裡是int型,而需要long型的情況等

4、符號引用驗證

這個階段的校驗發生在符號引用轉化為直接引用的時候,這個轉化動作發生在解析階段,,用來確保可以解析。通常校驗一下內容:

a、符號引用中通過字符串描述的全限定名能否找到對應的類

b、在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段

c、符號引用中的類、字段、方法是否可被當前類訪問。權限范圍如下圖所示:

\

准備

准備階段正式為類變量(static變量)分配內存並設置類變量初始值,這些變量所使用的內存都將在方法區中進行分配。

數據類型 零值 數據類型 零值 int 0 boolean false long 0L float 0.0f short (short)0 double 0.0d char '\u0000' reference null byte (byte)0    

解析

解析階段是jvm將常量池內的符號引用替換為直接引用的過程。先來看下符號引用和直接引用的區別:

符號引用:用一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要能定位到目標即可,此時目標並不一定已經加載到內存中。符號引用的字面量形式明確定義在java虛擬機規范的Class文件格式中。

直接引用:可以是直接指向目標的指針、相對偏移或者是一個能簡接定位到目標的句柄。此時目標必定已經在內存中存在。

解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。

初始化

初始化階段是類加載過程的最後一步,前面的類加載過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的java代碼(或者說是字節碼)。

在此階段,會首先執行static語句,包括類變量的定義和static{}語句塊,然後執行類的構造函數。靜態語句塊只能訪問到定義在靜態語句塊之前的變量,定義在它之後的 變量,在前面的靜態語句塊可以賦值,但是不能訪問。比如:

public class Test{

static {

i = 0;

//System.out.println(i);//這句編譯器會提示“非法向前引用”

}

static int i = 1;

public static void main(String[] args){

System.out.println(Test.i);//輸出是1

}

}

因為父類總是先執行初始化,這就意味著父類static語句>子類的static語句>父類的構造方法>子類的構造方法(>表示執行時間上先於)。

public class TestInit extends Parents{
static{
System.out.println("A1");
}
public TestInit() {
System.out.println("A2");
}

public static void main(String[] args){
new TestInit();
new TestInit();
}
}


class Parents {
static {
System.out.println("B1");
}
public Parents() {
System.out.println("B2");
}
}

上述代碼的執行結果是 B1 A1 B2 A2 B2 A2,同一個類加載器下,一個類型只會初始化一次。

另外需要提醒的是在接口中不能使用static語句塊,而且與類不同的是,只有在使用接口中定義的變量時,父接口才會初始化。

下面介紹下類加載器

類加載器采用雙親委派機制(Parents Delegation Model):如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父親加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啟動類加載器中,只有父類加載器反饋自己無法完成這個加載請求時,子類才會嘗試去加載。如下圖所示,前三個都是系統的加載器,最低層的也可以有用戶自己實現的加載器。下面是三個加載器的搜索范圍(也可以修改參數指向不同的范圍):

a、啟動類加載器:加載放在\lib中的類

b、擴展類加載器:加載\lib\ext目錄

c、負載加載用戶類路徑(ClassPath)上指定的類庫

\

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved