讀Java虛擬機(jī)類加載引發(fā)的血案
來自:花前月下的細(xì)說?|?責(zé)編:樂樂
鏈接:jianshu.com/u/ef2b368f8b29
??? ?
? ?正文? ?
/? ?前言? ?/
最近在看 Java 虛擬機(jī)類加載的知識(shí)點(diǎn),結(jié)果讓我發(fā)現(xiàn)了自己一個(gè)曾經(jīng)一直糾結(jié),又沒徹底弄懂的類加載黑洞,從而引發(fā)下面一系列的測(cè)試血案。
相信面試過的你們也會(huì)見過類似下面測(cè)試的這幾道題。不過,答案你真的理解了么?話不多說,直接 GKD??上也皇谴罄校?..哈哈哈 GKD 吧!下面就是測(cè)試過程種發(fā)現(xiàn)的一些疑惑點(diǎn),趕緊記錄一波...
/? ?正文? ?/
測(cè)試開始,先思考下下面代碼輸出什么:
class?Singleton?{
????public?Singleton()?{
????????System.out.println("Singleton?new?instance");
????}
????static?{
????????System.out.println("Singleton?static?block");
????}
????{
????????System.out.println("Singleton block ?。。?);
????}
}
public?class?NewTest?{
????public?static?void?main(String?args[]){
????????Singleton?singleton?=?new?Singleton();
????}
}
輸出結(jié)果:
Singleton?static?block
Singleton block ?。?!
Singleton?new?instance
當(dāng)然,大佬們應(yīng)該都能知道答案...畢竟,新手入門級(jí)的野怪,誰都打得過。這個(gè)對(duì)我這小菜雞也算還比較容易理解;加載連接過程,沒有需要處理的 static。new Singleton()?直接開始類的初始化了,所以輸出直接按照類的初始化順序來就好了
類的初始化的執(zhí)行順序
沒有父類的情況:
類的靜態(tài)屬性
類的靜態(tài)代碼塊
類的非靜態(tài)屬性
類的非靜態(tài)代碼塊
構(gòu)造方法
有父類的情況:
父類的靜態(tài)屬性
父類的靜態(tài)代碼塊
子類的靜態(tài)屬性
子類的靜態(tài)代碼塊
父類的非靜態(tài)屬性
父類的非靜態(tài)代碼塊
父類構(gòu)造方法
子類非靜態(tài)屬性
子類非靜態(tài)代碼塊
子類構(gòu)造方法
這里有個(gè)小誤區(qū),是我自己的誤區(qū)~~比如下面這個(gè)例子:
class?ParentSingleton{
public?static?int?value?=?100;
public?ParentSingleton(){
????System.out.println("ParentSingleton?new?instance");
}
static?{
????System.out.println("ParentSingleton?static?block");
}
{
????System.out.println("ParentSingleton block ?。。?");
}
}
當(dāng)要初始化上面這個(gè)類的時(shí)候,會(huì)輸出什么?
如果這時(shí)候,我們只看上面的初始化順序,會(huì)覺得這樣輸出,根據(jù)順序來嘛~
ParentSingleton?static?block
ParentSingleton block ?。?!
ParentSingleton?new?instance
???
OMG,錯(cuò)了,這里的順序不是說,只要初始化,就要全部按照順序一一執(zhí)行...不是這樣的。實(shí)際上只會(huì)輸出:
ParentSingleton?static?block
如果有創(chuàng)建這個(gè)類的實(shí)例,比如 new ParentSingleton(),才會(huì):
ParentSingleton block !??!?
ParentSingleton?new?instance
是的,這里的誤區(qū),我曾經(jīng)一度搞錯(cuò)了...尷尬。那再看這個(gè)測(cè)試:
class?Singleton?{
private?static?Singleton?singleton?=?new?Singleton();
private?Singleton()?{
????System.out.println("Singleton?new?instance");
}
public?static?void?forTest()?{
}
static?{
????System.out.println("Singleton?static?block");
}
{
????System.out.println("Singleton block ?。?!?");
}
}
public?class?TestSingleton?{
public?static?void?main(String?args[]){
????Singleton.forTest();
}
}
看完資料的我,逐漸膨脹,畢竟100多斤的胖子,我想的輸出應(yīng)該是:
在公眾號(hào)程序員小樂回復(fù)“offer”,獲取算法面試題和答案驚喜禮包。
Singleton?static?block
Singleton block ?。?!
Singleton?new?instance
然后運(yùn)行一看,懵逼了,結(jié)果是:
Singleton block !??!?
Singleton?new?instance
Singleton?static?block
咋回事啊,小老弟,結(jié)果亂套了...為什么不是先執(zhí)行 static 代碼塊先了。認(rèn)真想了一波,也不知道對(duì)不對(duì),只能瘋狂測(cè)試這樣子...
經(jīng)過一番測(cè)試,查看資料...最終...我覺得是這樣子的。整個(gè)的流程詳解應(yīng)該是執(zhí)行的第一步:Singleton.forTest();這時(shí)候,對(duì)Singleton類進(jìn)行加載和連接,所以首先需要對(duì)它進(jìn)行加載和連接操作。在連接-準(zhǔn)備階段,要講給靜態(tài)變量賦予默認(rèn)初始值,這里還沒到執(zhí)行 forTest;初始值是 singleton = null。加載和連接完畢之后,再進(jìn)行初始化工作:
private?static?Singleton?singleton?=?new?Singleton();
所以執(zhí)行去到了 new Singleton();??這里因?yàn)?new 會(huì)引起 Singleton 的初始化。需要執(zhí)行 Singleton構(gòu)造函數(shù)里面的內(nèi)容。但是又因?yàn)榉莝tatic初始化塊,這里面的代碼在創(chuàng)建java對(duì)象實(shí)例時(shí)執(zhí)行,而且在構(gòu)造器之前?。。。【褪沁@東西...所以輸出應(yīng)該是:
Singleton block ?。?!?
Singleton?new?instance
而根據(jù)類的初始化順序,要執(zhí)行 static 代碼塊,應(yīng)該輸出:
Singleton?static?block
完成初始化后。接下來就到真正調(diào)用 forTest 方法了,方法什么都不做,沒輸出。所以,總的答案就是:
Singleton block ?。?!?
Singleton?new?instance
Singleton?static?block
這里最大的原因就是,連接加載的時(shí)候,要給屬性初始化,而這里的初始化又剛好是 創(chuàng)建java 實(shí)例,需要執(zhí)行構(gòu)造,執(zhí)行構(gòu)造的前面又必須先執(zhí)行 {} 大括號(hào)非 static 塊。而不是和第一個(gè)測(cè)試?yán)幽菢樱瑂tatic 屬性不需要初始化,所以....
IG 永不加班,但我需要哇,繼續(xù)測(cè)試吧...繼續(xù)測(cè)試驗(yàn)證:
class?Singleton?{
private?static?Singleton?singleton?=?new?Singleton();
private?Singleton()?{
????System.out.println("Singleton?new?instance");
}
public?static?Singleton?getSingleton()?{
????return?new?Singleton();
}
static?{
????System.out.println("Singleton?static?block");
}
{
????System.out.println("Singleton block ?。?!?");
}
}
public?class?TestSingleton?{
public?static?void?main(String?args[]){
????Singleton?singleton?=?Singleton.getSingleton();
}
}
輸出結(jié)果如下所示。emm, 再次根據(jù)上面自己的理解,走一遍,應(yīng)該是:
Singleton block ?。。?
Singleton?new?instance
Singleton?static?block
Singleton block !??!?
Singleton?new?instance
這里后面第二次 new 為啥不引起第二次?類的初始化???因?yàn)橐粋€(gè)類只能初始化一次啊!new 只是創(chuàng)建實(shí)例,不再初始化了。所以在調(diào)用 getSingleton 的時(shí)候,只創(chuàng)建實(shí)例就好了,而創(chuàng)建實(shí)例就是:
Singleton block ?。?!?
Singleton?new?instance
在同一個(gè)類加載器下面只能初始化類一次,如果已經(jīng)初始化了就不必要初始化了。為什么只初始化一次呢?類加載的最終結(jié)果就是在堆中存有唯一一個(gè)Class對(duì)象,我們通過Class對(duì)象找到的那個(gè)唯一的。噢?運(yùn)行看一手,丟,對(duì)了..還有存在 final 的時(shí)候,和存在父類的時(shí)候,下面慢慢再測(cè)試驗(yàn)證....繼續(xù)測(cè)試:
class?Singleton?extends?ParentSingleton?{
public?Singleton()?{
????System.out.println("Singleton?new?instance");
}
static?{
????System.out.println("Singleton?static?block");
}
{
????System.out.println("Singleton block !??!?");
}
}
class?ParentSingleton{
public?ParentSingleton(){
????System.out.println("ParentSingleton?new?instance");
}
static?{
????System.out.println("ParentSingleton?static?block");
}
{
????System.out.println("ParentSingleton block ?。?!?");
}
}
public?class?TestSingleton?{
public?static?void?main(String?args[]){
????Singleton?singleton?=?new?Singleton();
}
}
輸出結(jié)果如下所示。這個(gè),很明了,還是按照上面的類的初始化,有父類的情況按順序調(diào)用,輸出如下:
ParentSingleton?static?block
Singleton?static?block
ParentSingleton block ?。。?
ParentSingleton?new?instance
Singleton block ?。。?
Singleton?new?instance
繼續(xù)測(cè)試如下所示。那個(gè)人,又來了...改成和上面沒有父類一樣的情況:
class?Singleton?extends?ParentSingleton?{
private?static?Singleton?singleton?=?new?Singleton();
private?Singleton()?{
????System.out.println("Singleton?new?instance");
}
public?static?Singleton?getSingleton()?{
????return?singleton;
}
static?{
????System.out.println("Singleton?static?block");
}
{
????System.out.println("Singleton block !?。?");
}
}
class?ParentSingleton{
public?ParentSingleton(){
????System.out.println("ParentSingleton?new?instance");
}
static?{
????System.out.println("ParentSingleton?static?block");
}
{
????System.out.println("ParentSingleton block ?。?!?");
}
}
public?class?TestSingleton?{
public?static?void?main(String?args[]){
????Singleton?singleton?=?Singleton.getSingleton();
}
}
輸出結(jié)果如下所示。這里,就開始懵了...有點(diǎn)。先看結(jié)果:
ParentSingleton?static?block
ParentSingleton block !?。?
ParentSingleton?new?instance
Singleton block ?。?!?
Singleton?new?instance
Singleton?static?block
其實(shí),很容易看清了,現(xiàn)在,再走一遍流程吧!執(zhí)行到 Singleton.getSingleton() 時(shí),先加載 Singleton ?,這時(shí)因?yàn)?Singleton 有父類,需要需要加載父類先,加載父類 ParentSingleton,根據(jù)加載流程,在連接-準(zhǔn)備階段,要講給靜態(tài)變量賦予默認(rèn)初始值,但父類沒有 static 屬性需要賦值初始化什么的,但是根據(jù)順序,需要初始化static 代碼塊:
ParentSingleton?static?block
這時(shí)候回到子類的加載流程。根據(jù)連接-準(zhǔn)備階段,子類有需要處理的屬性 private static Singleton singleton = new Singleton();賦值默認(rèn)值先,singleton = null;然后初始化 singleton = new Singleton();根據(jù)上面的經(jīng)驗(yàn),這里是創(chuàng)建實(shí)例 ,并引起初始化,正常應(yīng)該是:
Singleton block !??!?
Singleton?new?instance
Singleton?static?block
但是,重點(diǎn)來了 ??!類實(shí)例創(chuàng)建過程:按照父子繼承關(guān)系進(jìn)行初始化,首先執(zhí)行父類的初始化塊部分。然后是父類的構(gòu)造方法;再執(zhí)行本類繼承的子類的初始化塊,最后是子類的構(gòu)造方法,也就是:
在公眾號(hào)程序員小樂回復(fù)“Java”,獲取Java面試題和答案驚喜禮包。
ParentSingleton block ?。?!?
ParentSingleton?new?instance
同時(shí)子類的初始化,因?yàn)槌跏蓟宇愃懈割悾孕枰瘸跏蓟割悾ǖ沁@里因?yàn)楦割愐呀?jīng)初始化了,就不再初始化了)。所以結(jié)果是:
ParentSingleton?static?block
ParentSingleton block ?。?!?
ParentSingleton?new?instance
Singleton block ?。?!?
Singleton?new?instance
Singleton?static?block
最終測(cè)試如下所示:
class?Singleton?extends?ParentSingleton?{
private?static?Singleton?singleton?=?new?Singleton();
private?Singleton()?{
????System.out.println("Singleton?new?instance");
}
public?static?Singleton?getSingleton()?{
????return?singleton;
}
static?{
????System.out.println("Singleton?static?block");
}
{
????System.out.println("Singleton block ?。?!?");
}
}
class?ParentSingleton{
private?static?ParentSingleton?parentSingleton?=?new?ParentSingleton();???
public?ParentSingleton(){
????System.out.println("ParentSingleton?new?instance");
}
static?{
????System.out.println("ParentSingleton?static?block");
}
{
????System.out.println("ParentSingleton block !??!?");
}
}
public?class?TestSingleton?{
public?static?void?main(String?args[]){
????Singleton?singleton?=?Singleton.getSingleton();
}
}
測(cè)試結(jié)果如下所示:
ParentSingleton block ?。?!?
ParentSingleton?new?instance
ParentSingleton?static?block
ParentSingleton block ?。。?
ParentSingleton?new?instance
Singleton block ?。?!?
Singleton?new?instance
Singleton?static?block
加載一個(gè)類時(shí),先加載父類。按照先加載,創(chuàng)建實(shí)例,初始化,這個(gè)順序就發(fā)現(xiàn)很通順的寫出答案了。哈哈哈哈哈,終于清楚了。所以一切的一切,都是創(chuàng)建實(shí)例這個(gè)東西。搞得我頭暈。
部分特殊不引起類初始化記錄,先記錄下吧。
通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化,對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化
通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化
常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化
public?static?final?int?x?=6/3;??能夠在編譯時(shí)期確定的,叫做編譯常量,不會(huì)引起類的初始化!!!
public?static?final?int?x?=new?Random().nextInt(100);?運(yùn)行時(shí)才能確定下來的,叫做運(yùn)行時(shí)常量,運(yùn)行常量會(huì)引起類的初始化!!!
在虛擬機(jī)規(guī)范中使用了一個(gè)很強(qiáng)烈的限定語:“有且僅有”,這5種場景中的行為稱為對(duì)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用。
5種必須初始化的場景如下
1.????遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有初始化,則需要先觸發(fā)其初始化
這4條指令對(duì)應(yīng)的的常見場景分別是:使用new關(guān)鍵字實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。
注:靜態(tài)內(nèi)容是跟類關(guān)聯(lián)的而不是類的對(duì)象。
2. ???使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
注:反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法
對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性
這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為java語言的反射機(jī)制,這相對(duì)好理解為什么需要初始化類。
3. ???當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
注:子類執(zhí)行構(gòu)造函數(shù)前需先執(zhí)行父類構(gòu)造函數(shù)
4. ???當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
注:main方法是程序的執(zhí)行入口
5. ???當(dāng)使用JDK1.7的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化。則需要先觸發(fā)其初始化。
注:JDK1.7的一種新增的反射機(jī)制,都是對(duì)類的一種動(dòng)態(tài)操作
這回,以后看代碼的時(shí)候,就不會(huì)再被這些執(zhí)行加載順序弄混了,對(duì)優(yōu)化代碼可能還是有幫助的吧。
再不說,也能再讓我看到這些測(cè)試題,或者問我加載的過程,怎么也能處理回答個(gè)7788了吧。
可能其中個(gè)人理解有部分紕漏,還請(qǐng)大佬們指出~~蟹蟹鴨!
PS:歡迎在留言區(qū)留下你的觀點(diǎn),一起討論提高。如果今天的文章讓你有新的啟發(fā),歡迎轉(zhuǎn)發(fā)分享給更多人。
