參考:《深入理解Java虛擬機第三版》
《宋紅康JVM教程》
前言:JVM虛擬機運行的是字節碼文件,一個.java文件通過編譯變成一個.class字節碼文件,.class字節碼文件纔是JVM虛擬機需要的文件,但是.class文件中的類是什麼時候被加載,如何被加載的呢?本文便詳細介紹這幾個問題。
一、類的生命週期
類被加載的到虛擬機內存中開始,到卸載出內存結束,一共經過下列過程:
二、類的加載過程
1.加載
在加載階段,虛擬機需要完成以下三件事情:
- 通過一個類的全限定明獲取定義此類的二進制字節流;
- 將這個字節流所代表的的靜態存儲結構轉化爲方法區的運行時數據;
- 在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口
2.鏈接
2.1驗證
- 目的在於確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害虛擬機自身安全。
- 主要包括四種驗證,文件格式驗證,源數據驗證,字節碼驗證,符號引用驗證。
2.2準備
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這個時候進行內存分配的僅包括類變量,就是被static修飾的變量,不包括實例變量,實例變量的初始化過程不在這個階段,例如:
public static int value = 123;
那麼變量value在準備階段過後的值便是0。把value的值變成123的階段是初始化階段。
那麼如果是如下代碼呢??
public static final int value = 123;
在準備階段value的值已經是123了,因爲被final修飾的static,在編譯的時候就已經分配,準備階段會顯示初始化。
基本數據類型的零值:
2.3解析
解析階段是虛擬機將常量池內的符號引用轉換爲直接引用的過程
3.初始化
在初始化階段,才真正的去執行類中定義的Java代碼,在之前準備階段,對不被final修飾的static修飾的變量已經賦過初始值,而在初始化階段,則根據程序主觀計劃去初始化變量和其他資源。初始化階段是執行類構造器()方法的過程
- ()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合併而來。編譯器收集的順序是語句在源文件中出現的順序決定的,靜態語句塊只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量,靜態語句塊可以賦值,但是不能訪問
《深入理解JVM虛擬機第三版》中關於client方法第一條是如上描述的,可以通過以下程序來理解:
/**
* @author 四五又十
* @version 1.0
* @date 2020/7/3 16:28
*/
public class demo1 {
static {
num = 2;
}
private static int num = 1;
public static void main(String[] args) {
//輸出爲1
System.out.println(num);
}
}
初看如上的程序,讀者可能會覺得有點問題,因爲num變量定義在靜態代碼塊之後,但這樣是可以通過編譯的,因爲在準備階段已經將static修飾的變量賦予初值0,那麼在靜態代碼塊中可以訪問也就不是什麼很奇怪的事情
接着看如下代碼:
/**
* @author 四五又十
* @version 1.0
* @date 2020/7/3 16:28
*/
public class demo1 {
static {
num = 2;
System.out.println(num); //報錯
}
private static int num = 1;
public static void main(String[] args) {
System.out.println(num);
}
}
這段代碼在static靜態代碼塊中加入了訪問num的語句,這樣編譯器會報錯
Error:(14, 28) java: 非法前向引用
那麼這兩段程序很好的理解了,在上述描述中:靜態語句塊只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量,靜態語句塊可以賦值,但是不能訪問
- ()方法對於類或接口來說不是必要的,如果一個類沒有靜態語句塊,也沒有對類變量的複製操作,那麼編譯器不會爲這個類生成client方法
這點可以在idea中安裝jclasslib插件,反編譯字節碼文件
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳
當編譯好一個文件時,可以打開視圖
-
clinit()不同於類的構造器。(關聯:構造器是虛擬機視角下的init())若該類具有父類,jvm會保證子類的clinit()執行前,父類的clinit()已經執行完畢
-
虛擬機必須保證一個類的clinit()方法在多線程下被同步加鎖。