小編給大家分享一下java中類加載和類初始化順序的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
10年積累的網(wǎng)站制作、成都網(wǎng)站設(shè)計經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有鑲黃免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
1.對于靜態(tài)變量,靜態(tài)代碼塊,變量,初始化代碼塊,構(gòu)造器,他們的依次順序為,(靜態(tài)變量,靜態(tài)代碼塊)>(變量,初始化代碼塊)>構(gòu)造器
存在子類和父類的情況如下:
(1)單個類的執(zhí)行情況,代碼如下:
public class InitOrderTest {
// 靜態(tài)變量
public static String staticField = "靜態(tài)變量"; // 變量 public String field = "變量"; // 靜態(tài)初始化塊 static { System.out.println(staticField); System.out.println("靜態(tài)初始化塊"); } // 初始化塊 { System.out.println(field); System.out.println("初始化塊"); } // 構(gòu)造器 public InitOrderTest() { System.out.println("構(gòu)造器"); } public static void main(String[] args) { new InitOrderTest(); }
}
輸出如下:
(2)對于繼承的情況如下:
class Parent {
// 靜態(tài)變量
public static String p_StaticField = "父類--靜態(tài)變量";
protected int i = 1;
protected int j = 8;
// 變量
public String p_Field = "父類--變量";
// 靜態(tài)初始化塊 static { System.out.println(p_StaticField); System.out.println("父類--靜態(tài)初始化塊"); } // 初始化塊 { System.out.println(p_Field); System.out.println("父類--初始化塊"); } // 構(gòu)造器 public Parent() { System.out.println("父類--構(gòu)造器"); System.out.println("i=" + i + ", j=" + j); j = 9; }
}
public class SubClass extends Parent {
// 靜態(tài)變量 public static String s_StaticField = "子類--靜態(tài)變量"; // 變量 public String s_Field = "子類--變量"; // 靜態(tài)初始化塊 static { System.out.println(s_StaticField); System.out.println("子類--靜態(tài)初始化塊"); } // 初始化塊 { System.out.println(s_Field); System.out.println("子類--初始化塊"); } // 構(gòu)造器 public SubClass() { System.out.println("子類--構(gòu)造器"); System.out.println("i=" + i + ",j=" + j); } // 程序入口 public static void main(String[] args) { new SubClass(); }
}
輸出如下:
現(xiàn)在,結(jié)果已經(jīng)不言自明了。子類的靜態(tài)變量和靜態(tài)初始化塊的初始化是在父類的變量、初始化塊和構(gòu)造器初始化之前就完成了。
靜態(tài)變量、靜態(tài)初始化塊,變量、初始化塊初始化了順序取決于它們在類中出現(xiàn)的先后順序。
執(zhí)行過程分析
(1)訪問SubClass.main(),(這是一個static方法),于是裝載器就會為你尋找已經(jīng)編譯的SubClass類的代碼(也就是SubClass.class文件)。在裝載的過程中,裝載器注意到它有一個基類(也就是extends所要表示的意思),于是它再裝載基類。不管你創(chuàng)不創(chuàng)建基類對象,這個過程總會發(fā)生。如果基類還有基類,那么第二個基類也會被裝載,依此類推。
(2)執(zhí)行根基類的static初始化,然后是下一個派生類的static初始化,依此類推。這個順序非常重要,因為派生類的“static初始化”有可能要依賴基類成員的正確初始化。
(3)當(dāng)所有必要的類都已經(jīng)裝載結(jié)束,開始執(zhí)行main()方法體,并用new SubClass()創(chuàng)建對象。
(4)類SubClass存在父類,則調(diào)用父類的構(gòu)造函數(shù),你可以使用super來指定調(diào)用哪個構(gòu)造函數(shù)(也就是Beetle()構(gòu)造函數(shù)所做的第一件事)。
基類的構(gòu)造過程以及構(gòu)造順序,同派生類的相同。首先基類中各個變量按照字面順序進行初始化,然后執(zhí)行基類的構(gòu)造函數(shù)的其余部分。
(5)對子類成員數(shù)據(jù)按照它們聲明的順序初始化,執(zhí)行子類構(gòu)造函數(shù)的其余部分。
接下來分析java的類加載機制,在分析類加載機制之前,請看下面一道題:
class Singleton {
private static Singleton singleton = new Singleton();
public static int value1;
public static int value2 = 0;
private Singleton() { value1++; value2++; } public static Singleton getInstance() { return singleton; }
}
class Singleton2 {
public static int value1;
public static int value2 = 0;
private static Singleton2 singleton2 = new Singleton2();
private Singleton2() { value1++; value2++; } public static Singleton2 getInstance2() { return singleton2; }
}
public class TestClassLoader {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println("Singleton1 value1:" + singleton.value1);
System.out.println("Singleton1 value2:" + singleton.value2);
Singleton2 singleton2 = Singleton2.getInstance2(); System.out.println("Singleton2 value1:" + singleton2.value1); System.out.println("Singleton2 value2:" + singleton2.value2); }
}
輸出結(jié)果如下:
jvm類加載分為5個過程:加載,驗證,準(zhǔn)備,解析,初始化,使用,卸載,如下所示:
下面來看看加載,驗證,準(zhǔn)備,解析,初始化這5個過程的具體動作。
1.1 加載
加載主要是將.class文件(并不一定是.class??梢允荶IP包,網(wǎng)絡(luò)中獲?。┲械亩M制字節(jié)流讀入到JVM中。
在加載階段,JVM需要完成3件事:
1)通過類的全限定名獲取該類的二進制字節(jié)流;
2)將字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu);
3)在內(nèi)存中生成一個該類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。
1.2 連接
1.2.1 驗證
驗證是連接階段的第一步,主要確保加載進來的字節(jié)流符合JVM規(guī)范。
驗證階段會完成以下4個階段的檢驗動作:
1)文件格式驗證
2)元數(shù)據(jù)驗證(是否符合Java語言規(guī)范)
3)字節(jié)碼驗證(確定程序語義合法,符合邏輯)
4)符號引用驗證(確保下一步的解析能正常執(zhí)行)
1.2.2 準(zhǔn)備
準(zhǔn)備是連接階段的第二步,主要為靜態(tài)變量在方法區(qū)分配內(nèi)存,并設(shè)置默認(rèn)初始值。
1.2.3 解析
解析是連接階段的第三步,是虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程。
1.3 初始化
初始化階段是類加載過程的最后一步,主要是根據(jù)程序中的賦值語句主動為類變量賦值。
注:
1)當(dāng)有父類且父類為初始化的時候,先去初始化父類;
2)再進行子類初始化語句。
什么時候需要對類進行初始化?
1)使用new該類實例化對象的時候;
2)讀取或設(shè)置類靜態(tài)字段的時候(但被final修飾的字段,在編譯器時就被放入常量池的靜態(tài)字段除外static final);
3)調(diào)用類靜態(tài)方法的時候;
4)使用反射Class.forName(“xxxx”)對類進行反射調(diào)用的時候,該類需要初始化;
5) 初始化一個類的時候,有父類,先初始化父類(注:1. 接口除外,父接口在調(diào)用的時候才會被初始化;2.子類引用父類靜態(tài)字段,只會引發(fā)父類初始化);
6) 被標(biāo)明為啟動類的類(即包含main()方法的類)要初始化;
7)當(dāng)使用JDK1.7的動態(tài)語言支持時,如果一個java.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應(yīng)的類沒有進行過初始化,則需要先觸發(fā)其初始化。
以上情況稱為對一個類進行主動引用,且有且只要以上幾種情況需要對類進行初始化。
再回過頭來分析一開始的面試題:
Singleton輸出結(jié)果:1 0
原因:
1 首先執(zhí)行main中的Singleton singleton = Singleton.getInstance();
2 類的加載:加載類Singleton
3 類的驗證
4 類的準(zhǔn)備:為靜態(tài)變量分配內(nèi)存,設(shè)置默認(rèn)值。這里為singleton(引用類型)設(shè)置為null,value1,value2(基本數(shù)據(jù)類型)設(shè)置默認(rèn)值0
5 類的初始化(按照賦值語句進行修改):
執(zhí)行private static Singleton singleton = new Singleton();
執(zhí)行Singleton的構(gòu)造器:value1++;value2++; 此時value1,value2均等于1
執(zhí)行
public static int value1;
public static int value2 = 0;
此時value1=1,value2=0
Singleton2輸出結(jié)果:1 1
原因:
1 首先執(zhí)行main中的Singleton2 singleton2 = Singleton2.getInstance2();
2 類的加載:加載類Singleton2
3 類的驗證
4 類的準(zhǔn)備:為靜態(tài)變量分配內(nèi)存,設(shè)置默認(rèn)值。這里為value1,value2(基本數(shù)據(jù)類型)設(shè)置默認(rèn)值0,singleton2(引用類型)設(shè)置為null,
5 類的初始化(按照賦值語句進行修改):
執(zhí)行
public static int value2 = 0;
此時value2=0(value1不變,依然是0);
執(zhí)行
private static Singleton singleton = new Singleton();
執(zhí)行Singleton2的構(gòu)造器:value1++;value2++;
此時value1,value2均等于1,即為最后結(jié)果
類加載器之間的層次關(guān)系如下:
類加載器之間的這種層次關(guān)系叫做雙親委派模型。
雙親委派模型要求除了頂層的啟動類加載器(Bootstrap ClassLoader)外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不是以繼承關(guān)系實現(xiàn)的,而是用組合實現(xiàn)的。
雙親委派模型的工作過程
如果一個類接受到類加載請求,他自己不會去加載這個請求,而是將這個類加載請求委派給父類加載器,這樣一層一層傳送,直到到達啟動類加載器(Bootstrap ClassLoader)。
只有當(dāng)父類加載器無法加載這個請求時,子加載器才會嘗試自己去加載。
雙親委派模型的代碼實現(xiàn)
雙親委派模型的代碼實現(xiàn)集中在java.lang.ClassLoader的loadClass()方法當(dāng)中。
1)首先檢查類是否被加載,沒有則調(diào)用父類加載器的loadClass()方法;
2)若父類加載器為空,則默認(rèn)使用啟動類加載器作為父加載器;
3)若父類加載失敗,拋出ClassNotFoundException 異常后,再調(diào)用自己的findClass() 方法。
loadClass源代碼如下:
破壞雙親委派模型
雙親委派模型很好的解決了各個類加載器加載基礎(chǔ)類的統(tǒng)一性問題。即越基礎(chǔ)的類由越上層的加載器進行加載。
若加載的基礎(chǔ)類中需要回調(diào)用戶代碼,而這時頂層的類加載器無法識別這些用戶代碼,怎么辦呢?這時就需要破壞雙親委派模型了。
下面介紹兩個例子來講解破壞雙親委派模型的過程。
JNDI破壞雙親委派模型
JNDI是Java標(biāo)準(zhǔn)服務(wù),它的代碼由啟動類加載器去加載。但是JNDI需要回調(diào)獨立廠商實現(xiàn)的代碼,而類加載器無法識別這些回調(diào)代碼(SPI)。
為了解決這個問題,引入了一個線程上下文類加載器。 可通過Thread.setContextClassLoader()設(shè)置。
利用線程上下文類加載器去加載所需要的SPI代碼,即父類加載器請求子類加載器去完成類加載的過程,而破壞了雙親委派模型。
Spring破壞雙親委派模型
Spring要對用戶程序進行組織和管理,而用戶程序一般放在WEB-INF目錄下,由WebAppClassLoader類加載器加載,而Spring由Common類加載器或Shared類加載器加載。
那么Spring是如何訪問WEB-INF下的用戶程序呢?
使用線程上下文類加載器。 Spring加載類所用的classLoader都是通過Thread.currentThread().getContextClassLoader()獲取的。當(dāng)線程創(chuàng)建時會默認(rèn)創(chuàng)建一個AppClassLoader類加載器(對應(yīng)Tomcat中的WebAppclassLoader類加載器): setContextClassLoader(AppClassLoader)。
利用這個來加載用戶程序。即任何一個線程都可通過getContextClassLoader()獲取到WebAppclassLoader。
看完了這篇文章,相信你對“java中類加載和類初始化順序的示例分析”有了一定的了解,如果想了解更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!