類加載的整個過程如下圖所示:包括加載,驗證,準備,解析,初始化,使用,卸載整個流程。
一、加載
目的:通過一個類的全限定名來獲取定義此類的二進制字節流,並將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構,並在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口。
其中獲取Class文件可以有如下途徑:
1.從zip壓縮包中讀取;
2.從網絡中獲取;
3.運行時計算生成;
4.由其他文件生成;
5.從數據庫中讀取;
6.可以從加密文件中獲取
二、驗證
目的:確保Class文件的字節流中包含的信息符合《java虛擬機規範》的全部約束。
過程:驗證主要包含四個驗證過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證。
1.文件格式驗證:
1.是否以魔數0xCAFEBABE開頭;
2.主、次版本號是否在當前Java虛擬機接受範圍之內;
3.常量池的常量中是否有不被支持的常量類型(檢查常量tag標誌);
4.指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量;
5.CONSTANT_Utf8_info型的常量中是否有不符合UTF-8編碼的數據;
6.Class文件中各個部分及文件本身是否有被刪除的或附加的其他信息。
2.元數據驗證
1.這個類是否有父類(除了java.lang.Object之外,所有的類都應當有父類);
2.這個類的父類是否繼承了不允許被繼承的類(被final修飾的類);
3.如果這個類不是抽象類,是否實現了其父類或接口之中要求實現的所有方法;
4.類中的字段、方法是否與父類產生矛盾(如:覆蓋了父類的final字段,或者出現不符合規則的方法重載等)
3.字節碼驗證
1.保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作(如:不會出現類似於“在操作數棧放置了一個int類型的數據,使用時卻按long類型來加載入本地變量表中”這樣的情況);
2.保證任何跳轉指令都不會跳轉到方法體以外的字節碼指令上;
3.保證方法體中的類型轉換總是有效的(如:可以把一個子類對象賦值給父類數據類型,但不能把一個父類對象賦值給子類數據類型)
4.符號引用驗證
1.符號引用中通過字符串描述的全限定名是否能找到對應的類;
2.在指定類中是否存在符合方法的字段描述符及簡單名稱所描述的方法和字段;
3.符號引用中的類、字段、方法的可訪問性是否可被當前類訪問
三、準備
目的:是正式爲類中定義的變量(靜態變量,被static修飾的變量)分配內存並設置類變量初始值的階段。注意:此時只是賦予初始化值,並沒有真正的進行賦值操作;例如:
public static int a = 1
在準備階段a的值是初始值0,並不是1,賦值操作是在初始化進行的。所有類型的默認值如下所示:
數據類型 | 初始值 |
int | 0 |
long | 0L |
short | (short)0 |
char | ‘\u0000’ |
byte | (byte)0 |
boolean |
false |
float | 0.0f |
double | 0.0d |
reference | null |
四、解析
目的:java虛擬機將常量池內的符號引用替換爲直接引用的過程。其中符號引用和直接引用解釋如下:
符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。
直接引用:直接引用是可以直接指向目標的指針、相對偏移量或者是一個能間接定位到目標的句柄。
解析作用主要是針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符這7類符號引用進行。
五、初始化
目的:是真正執行類中編寫java程序代碼的過程;就是執行類構造器<clinit>()的過程。
說明:<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊(static{}塊)中的語句合併產生的。執行<clinit>()方法時,不需要再去執行父類的類構造器,因爲虛擬機保證了在執行子類的類構造器之前,父類的類構造器一定是已經執行完的,所以第一個執行的類構造器是java.lang.Object,所以父類中的靜態代碼塊先於子類執行;<clinit>()方法,並不是每一個類或接口都有的方法,當一個類中沒有靜態變量和靜態代碼塊時,編譯器就不會爲該類或接口生成此方法;