類加載的程序
類加載由7個步驟完成,看圖

加載
1、通過類的全限定名獲取存盤該類的class檔案(沒有指明必須從哪獲取)
2、決議成運行時資料,即instanceKlass實體,存放在方法區
3、在堆區生成該類的Class物件,即instanceMirrorKlass實體
何時加載
虛擬機規范中并沒有強制約束何時進行加載,但是規范嚴格規定了有且只有下列五種情況必須對類進行加載(加載、驗證、準備都會隨之發生),稱為主動參考:
1、遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有被加載,則需要先加載,
生成這4條指令的最常見的Java代碼場景是:
- 使用new關鍵字實體化物件的時候
- 讀取或設定一個類的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候
- 呼叫一個類的靜態方法的時候
2、使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒有被加載,則需要先加載,
3、當加載一個類的時候,如果發現其父類還沒有被加載,則需要先加載其父類,
4、當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先加載這個主類(當然如果主類存在未加載的父類,會先加載父類),
5、當使用JDK1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實體最后的決議結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有被加載,則需要先加載,
預加載:
包裝類、String、Thread
因為沒有指明必須從哪獲取class檔案,腦洞大開的工程師們開發了這些
1、從壓縮包中讀取,如jar、war
2、從網路中獲取,如Web Applet
3、動態生成,如動態代理、CGLIB
4、由其他檔案生成,如JSP
5、從資料庫讀取
6、從加密檔案中讀取
接下來直接上一些測驗例子及結果說明:
測驗代碼1
public class Test_1 { public static void main(String[] args) { System.out.println(Test_1_B.str); while (true); } } class Test_1_A{ public static String str = "A str"; static { System.out.println("A Static Block"); } } class Test_1_B extends Test_1_A{ static { System.out.println("B Static Block"); } }
測驗結果:

原因分析:
本示例看似滿足加載時機的第一條:當要獲取某一個類的靜態成員變數的時候如果該類尚未加載,則對該類進行加載,但對于靜態欄位,只有直接定義這個欄位的類才會被加載,因此通過其子類來參考父類中定義的靜態欄位屬于間接參考,只會觸發父類的加載而不會觸發子類的加載,
測驗代碼2
public class Test_2 { public static void main(String[] args) { System.out.printf(Test_2_B.str); } } class Test_2_A { static { System.out.println("A Static Block"); } } class Test_2_B extends Test_2_A { public static String str = "B str"; static { System.out.println("B Static Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第一條:當要獲取某一個類的靜態成員變數的時候如果該類尚未加載,則對該類進行加載,
本示例滿足加載時機的第三條:當加載一個類的時候,如果發現其父類還沒有被加載,則需要先加載其父類,
但對于靜態欄位,只有直接定義這個欄位的類才會被加載,此示例對于靜態欄位的參考是直接參考,所以會觸發子類的加載,
測驗代碼3
public class Test_3 { public static void main(String[] args) { System.out.printf(Test_3_B.str); } } class Test_3_A { public static String str = "A str"; static { System.out.println("A Static Block"); } } class Test_3_B extends Test_3_A { public static String str = "B str"; static { System.out.println("B Static Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第一條:當要獲取某一個類的靜態成員變數的時候如果該類尚未加載,則對該類進行加載,
本示例滿足加載時機的第三條:當加載一個類的時候,如果發現其父類還沒有被加載,則需要先加載其父類,
但對于靜態欄位,只有直接定義這個欄位的類才會被加載,此示例對于子類的靜態欄位對父類的靜態欄位進行了覆寫,參考使用是直接參考,所以會觸發子類的加載,在加載子類的時候,會優先加載其父類,
測驗代碼4
public class Test_4 { public static void main(String[] args) { Test_4[] arrs =new Test_4[1]; } } class Test_4_A{ static { System.out.println("Test_4_A Static Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第四條:當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先加載這個主類(當然如果主類存在未加載的父類,會先加載父類),
雖然Test_4_A這個類和包含main方法的主類寫在同一個檔案中,但是編譯器編譯后,任然生產兩個對立的class檔案,虛擬機啟動時,按需加載只會加載Test_4這個類,
測驗代碼5
public class Test_5 { public static void main(String[] args) { Test_5_A[] obj = new Test_5_A[1]; } } class Test_5_A { static { System.out.println("Test_5_A Static Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第四條:當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先加載這個主類(當然如果主類存在未加載的父類,會先加載父類),
這個程序看似也滿足加載時機的第一條:遇到new創建物件時若類沒被加載,則加載該類,運行之后發現沒有輸出“Test_5_A Static Block”,說明并沒有觸發類cn.jvm.classload.Test_5_A的加載階段,但是這段代碼里面觸發了另外一個名為 [Lcom.jvm.classload.Test_5_A 的類的加載階段,對于用戶代碼來說,這并不是一個合法的類名稱,它是一個由虛擬機自動生成的、直接繼承于java.lang.Object的子類,創建動作由位元組碼指令anewarray觸發,
這個類代表了一個元素型別為cn.jvm.classload.Test_5_A的一維陣列,陣列中應有的屬性和方法(用戶可直接使用的只有被修飾為public的length屬性和clone()方法)都實作在這個類里,
簡言之,現在通過new要創建的是一個Test_5_A陣列物件,而非Test_5_A類物件,因此也屬于間接參考,不會加載Test_5_A類,
測驗代碼6
public class Test_6 { public static void main(String[] args) { Test_6_A obj = new Test_6_A(); } } class Test_6_A { static { System.out.println("Test_6_A Static Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第一條:使用new關鍵字實體化類物件的時候,如果該類尚未加載,則對該類進行加載,測驗代碼7
public class Test_7 { public static void main(String[] args) { System.out.println(Test_7_A.str); } } class Test_7_A{ public static final String str = "A Str"; static { System.out.println("Test_7_A Static Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第一條:讀取或設定一個類的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外)的時候, Test_7_A類在編譯時已經將靜態屬性的常量值存入常量池,Test_7在編譯時也直接指向了常量池,所以不需要加載Test_7_A類,見下圖:


測驗代碼8
public class Test_8 { public static void main(String[] args) { System.out.println(Test_8_A.uuid); } } class Test_8_A{ public static final String uuid= UUID.randomUUID().toString(); static { System.out.println("Test_8_A Static Block"); } }
測驗結果:

原因分析:
本示例看似也滿足加載時機的第一條:讀取或設定一個類的靜態欄位,此屬性被final修飾,應該不加載Test_8_A類,但是結果為什么會顯示加載了Test_8_A類呢?
這是因為被final修飾的靜態屬性的值不是常量值,無法再編譯時確定值,只有在運行是才能進行讀取,所以需要加載Test_8_A類,詳見下圖:


測驗代碼9
public class Test_9 { static { System.out.println("Test_9 Static Block"); } public static void main(String[] args) throws ClassNotFoundException { Class<?> clazz = Class.forName("com.jvm.classload.Test_1_A"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第二條:使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒有被加載,則需要先加載,
本示例滿足加載時機的第四條:當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先加載這個主類(當然如果主類存在未加載的父類,會先加載父類),
測驗代碼10
public class Test_10 { public static void main(String[] args) { System.out.println(new Test_10_B().str); } } class Test_10_A{ static { System.out.println("A Static Block"); } } class Test_10_B extends Test_10_A{ public String str="B Str"; static { System.out.println("B Statci Block"); } }
測驗結果:

原因分析:
本示例滿足加載時機的第一條:使用new關鍵字實體化物件的時候,如果類沒有被加載,則需要先加載,
本示例滿足加載時機的第三條:當加載一個類的時候,如果發現其父類還沒有被加載,則需要先加載其父類,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/110734.html
標籤:Java
上一篇:[Java] 封裝zip內檔案處理的函式,演示修改zip內的txt追加文本
下一篇:Java動態代理
