一,類加載機制
虛擬機把描述類的數據從Class文件加載到內存,并對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制
二,類加載的時機----什么時候類進行加載?
- 遇到new,getstatic,putstatic和invokestatic這四條字節碼指令時,如果沒有進行過初始化,則需要先觸發其初始化
- new:使用new關鍵字實例化對象的時候
- getstatic:設置一個類的靜態字段
- putstatic:讀取一個類的靜態字段
若靜態字段被final修飾,已經在編譯期把結果放入常量池的靜態字段除外 - 調用一個類的靜態方法的時候
- 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化
- 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類初始化
- 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類
- 當使用JDK1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果REF_getstatic, REF_putstatic, REF_invokestatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化
需要注意的是:
1,對于靜態字段,只有直接定義這個字段的類才會被初始化,因此通過子類來引用父類中定義的靜態字段,
只會觸發父類的初始化而不會觸發子類的初始化
2,常量(final)在編譯階段會存入調用類的常量池中,本質上并沒有直接引用到定義常量的類,因此不會
觸發定義常量的類的初始化
3,當接口初始化時,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時候(如引用接口
中定義的常量)才會初始化.
三,類加載的過程
java虛擬機中類加載的全過程----加載,鏈接(驗證,準備,解析),初始化
1,加載
在加載階段,虛擬機需要完成以下三件事情
- 通過一個類的全限定名來獲取定義此類的二進制字節流
- 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
- 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口
2,驗證
- 文件格式驗證
主要是驗證字節流是否符合Class文件格式的規范 - 元數據驗證
對字節碼描述的信息進行語義分析,保證其描述的信息符合Java語言規范的要求
-字節碼驗證
通過數據流和控制流分析,確定程序語義是合法的,符合邏輯的 - 符號引用驗證
虛擬機將符號引用轉化為直接引用的時候,這個轉化動作將在解析階段發生
3,準備
準備階段是正式為類變量分配內存并設置初始化值的階段.這些變量所使用的內存都將在方法區中進行分配
4,解析
- 類或接口的解析
- 字段解析
- 類方法解析
- 接口方法解析
5,初始化
初始化階段是執行類構造器<clinit>()方法的過程
- <clinit>()方法怎么生成的呢?
<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊塊(static{})中的語句合并產生的,編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊只能訪問到定義在靜態語句塊之前的變量;定義在他之后的變量,在前面的靜態語句塊可以賦值,但是不能訪問. - <clinit>()方法的一些特點
- <clinit>()方法與類的構造方法(或者說實例構造器<init>())不同,他不需要顯式地調用父類構造器,虛擬機會保證子類的<clinit>()方法執行之前,父類的<clinit>()已經執行完畢. 因此在虛擬機中第一個被執行<clinit>()的類是java.lang.Object
- 由于父類的<clinit>()方法先執行,也就意味著父類中定義的靜態語句塊要優先于子類的變量賦值操作
- <clinit>()對于類或接口來說不是必須的,如果一個類中沒有靜態語句塊,也沒有對靜態變量的賦值操作**,那么編譯器可以不為這個類生成<clinit>()
- 接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會生成<clinit>()方法.但是接口與類不同,執行接口的<clinit>()方法不需要先執行父接口的<clinit>()方法.只有當父接口中定義的變量使用時,父接口才會初始化.另外,接口的實現類在初始化時也一樣不會執行接口的<clinit>()方法.
- 虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確的執行
四,練習題
1,打印結果是什么?
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
2,打印結果是什么?
public class Test {
public static int k = 0;
public static Test t1 = new Test("t1");
public static Test t2 = new Test("t2");
public static int i = print("i");
public static int n = 99;
private int a = 0;
public int j = print("j");
{
print("構造塊");
}
static {
print("靜態塊");
}
public Test(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
Test t = new Test("init");
}
}
參考:
1,<<深入理解Java虛擬機 JVM高級特性與最佳實踐 第二版 周志明>>