寫給小哥哥看的Java類加載全過程(小姐姐直呼哇塞)

大家好,我是node哥哥,一個被Bug耽誤了才藝的程序員,專注於Java領域的知識分享和技術交流,每天會以各種好看好玩的形式帶給大家Java學習的小技巧,喜歡我的同學可以點贊+關注哦~

咳咳,最近劇本寫得有點感覺了哈哈,越寫越舒服,在此確定了以後文章的風格就是劇本+段子的形式哈。用大白話講技術,用吹牛逼的方式討論原理,不管你是小白還是大白,看了我的文章包你爽就完了…當然爽完能學到知識,何樂而不爲呢?別問,問就是牛逼!

牛逼

今天故事的主角是剛認識的新朋友陽哥。這小夥,還是挺靦腆的,平時斯斯文文的,不怎麼說話。我好話求了好半天,並許諾給他隔壁班新來小姐姐的微信,陽哥才終於答應爆照!
陽哥
(陽哥本尊)

哈哈~別看陽哥這麼可愛迷人,學習Java那可是一點都不耽誤啊,每天早上5點起牀,晚上搞到12點多才睡,這學習的勁頭直逼當年的我啊,不得不佩服!
在這裏插入圖片描述

故事

話說那是一個夏日的午後,空氣中瀰漫着梔子花的香味~我們的陽哥卻無心賞花,正坐在小板凳上埋頭研讀《Java從入門到如土》第4版,只見他雙眉緊鎖,苦苦思考。那副神情,不知道的還以爲他在驗證哥德巴赫猜想

這時,我悄悄靠近,不動聲色,想瞄一眼陽哥在學啥高深的學問。

Java的類加載機制這幾個大字標題映入我的眼前。呵~ 我輕蔑的一笑,一屁股坐在陽哥旁邊,準備啃我剛買的內蒙古奶油冰棍兒。

陽哥這時才注意到有人來,略微收起鼓了很久的腮幫子。有人打擾他獨立思考讓他很不開心,回頭發出一聲豬叫(額,說錯了,說錯了,只是嘟囔了一句…),一看是我,眼睛突然一亮:哎,node老哥,你怎麼來了?(順手搶走了我剛拆開包裝的冰棍兒)

我順手準備奪回,卻未料到陽哥身手敏捷,書都扔地上了,慌忙把冰棍兒含在嘴裏…

:臥槽,陽哥,你過分了,我來指導你學習的,你卻搶我冰棍吃,做人怎麼這麼不厚道?

陽哥:(笑出了豬叫)哈哈哈…嗝…你說做人?

:哦,我忘記了,你是迷人的小可愛…

陽哥:滾!!(準備發怒)

:好了,好了…看在你一個人這麼寂寞的份上,冰棍算我請你的了,可以不?

陽哥:不可以!你一說我就來氣。上次你說給我介紹小姐姐一起學習Java,結果那天下午我沒睡午覺就出了宿舍,特地找隔壁宿舍老大哥借了200塊錢搞了一個最近很流行的錫紙燙,還買了一雙最新款的AK,想給小姐姐一個好印象。結果那天我在圖書館角落裏足足等了她倆小時,Java編程思想都快翻完一遍了,人還不來!臨走的時候,看到她跟另一個哥們手牽手一起從我旁邊路過??還跟我打了個招呼?我嘞個擦???
小朋友
:害!我說給你介紹小姐姐認識,又沒說她單身!你可能是誤會我的意思了哈…我是說那個小姐姐是學霸,Java學的很6,你可以跟她請教疑惑,不是你想的那種的啊…陽哥,你是不是最近一個人太寂寞了?要不…

陽哥:(臉色一陣青一陣紅,感覺隨時要火山爆發)你*的!

:(趕緊岔開話題)額,陽哥…那個…我不也是一番好心嗎?別爆粗口啊!剛纔看你好像在看那本最近很火的Java書?看的咋樣了?有沒有新的學習感悟分享分享?

果然,陽哥是個十足的Java迷,一聽說討論Java,冰棍都不吃了,馬上撿起書開始跟我掰扯,我悄悄的鬆了一口氣…

陽哥:哈哈,我跟你說老哥,不是我吹牛逼,我TM就是個天才!你別看我學Java時間短,但是我卻對原理研究的非常深入!你看這道題,我們班沒一個人會,我卻把它搞得明明白白,已經完全分析透了,要不我給你也講講?

我半信半疑地拿過來陽哥的書,果然看到Java的類加載機制這一章後面有幾道練習題,前面幾題確實確實比較基礎,最後一題題目還挺長的:

題目

// 有下面的一個類,請問最後main方法輸出是什麼?

public class Main {
    public static int k = 0;
    static Main t1 = new Main("t1");
    public static Main t2 = new Main("t2");
    public static int i = print("i");
    public static int n = 99;
    public int j = print("j");

    static {
        print("靜態塊");
    }

    public Main(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) {
        new Main("init");
    }
}

拿到這個題目,說實話有點繞,但我覺得難不倒我,畢竟混跡Java這麼多年了,別問,問就是牛逼!

那麼牛逼回來了,這個輸出到底是什麼呢?我開始有點慌了…

:額,那個…陽哥啊,剛纔買冰棍的時候我忘記付錢了,我先過去把賬結了哈,畢竟咱們都是高素質的人不是?不能白嫖啊!

陽哥:(很鄙視的看了我一眼)做不出來就直說,裝什麼大佬?

:別扯了,這啥基礎題啊這麼簡單,你等我5分鐘,我回來給你講的明明白白!

陽哥更加不屑了:好!我就在這等你,5分鐘你要是做不出來,你直播吃**!

:(有點心虛,但氣勢不能輸)好!你等着吧,我付完賬回來你看我怎麼把它安排了吧!

說完我就頭也不回地溜了。

說實話,這道題真的有點把我整懵了,居然一點思路都沒有?怎麼那麼多個靜態成員變量…又是靜態變量,又是靜態代碼塊,還有靜態方法…構造函數…這都啥啊,忒複雜了吧!不行不行,我得趕緊回去充充電,不能讓陽哥嘲笑我啊!不然這以後怎麼混?

要知道這道題怎麼做,我們先看下類加載的過程。

類加載過程

類加載
當我們使用main方法new一個對象的時候,其實是類在JVM(Java虛擬機)中被加載的過程。如果該類還未被加載到內存中,則JVM會通過加載、鏈接(驗證、準備、解析)、初始化這5個步驟來對該類進行初始化。

五個階段中加載,驗證,準備和初始化發生的順序是確定的,解析過程則不確定,我們都知道java支持動態綁定(多態),運行時才知道最終對象的引用,所以解析階段可能發生在初始化階段之後。雖然這幾個階段是按順序開始的,但他們不是按順序執行的,也就是說他們的執行和完成的時間不分先後,通常都是相互交叉進行。

JVM中Java類會在首次被使用時執行初始化,爲類的(靜態)變量賦予正確的初始值(例如int類型的變量初始值爲0)。但這不代表類加載器只有等到某個類被首次主動使用時才加載,JVM規範允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)如果這個類一直沒有被程序主動使用,那麼類加載器就不會報告錯誤。

下面我們來針對類加載的這整個過程逐一進行拆分講解:

1.加載階段

類的加載階段是指的是將類的class文件讀入到內存,併爲之創建一個java.lang.Class對象。類加載主要做下面幾件事情:

  • 通過一個類的全限定名來獲取定義此類的二進制字節流。
  • 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時存儲結構。
  • 在內存中生成一個代表這個類的 Class 對象,作爲方法區這個類的各種數據的訪問入口。

類加載器通常無須等到首次使用該類時才加載該類,Java虛擬機規範允許系統預先加載某些類。類的來源通常有以下幾種:

  • 本地編譯好的class文件
  • jar包中的class文件
  • 動態代理生成的class文件
  • 壓縮文件中的jar,class,war文件

2.驗證階段

此階段主要爲保證加載類的正確性。

主要根據以下幾種方法對類信息進行驗證:

  • 主要驗證字節流是否符合Class文件格式規範,並且能被當前的虛擬機加載處理。b比如常量池中是否有不被支持的常量類型,指向常量的中的索引值是否存在不存在的常量或不符合類型的常量。
  • 元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規範的要求。
  • 字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。
  • 符號引用驗證:確保解析動作能正確執行。

3.準備階段

類準備階段負責爲類的靜態變量分配內存,並設置默認初始值,注意是虛擬機給各個類型的變量定義的默認值,不是實際程序中的賦值。例如:

public staic int a = 1;  

準備階段a的值爲默認值0,而不是1

對於基本類型的靜態變量一般被賦予0,對於引用的靜態類型,一般被賦予null,如果是boolean型數據,則爲false,對於final修飾的變量,會在這個階段就直接賦值,成員變量則不會賦初始值。

4.解析階段

把類中的符號引用轉化爲直接引用。

什麼是符號引用?

符號引用是以一組符號來描述所引用的目標,符號可以是任何的字面形式的字面量,只要不會出現衝突能夠定位到就行。佈局和內存無關,引用的目標不一定已經加載到內存中

什麼是直接引用?

是指向目標的指針,偏移量或者能夠直接定位的句柄。該引用是和內存中的佈局有關的,並且一定是已經加載進來的

虛擬機的解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號的引用進行。

5.初始化階段

初始化階段就是爲類的靜態變量依次賦予實際的初始值(注意與前面的默認值區分開,這裏指的是賦予代碼中的值),包括靜態代碼塊也會執行。前面的代碼:

public staic int a = 1;  

在這個時候a就是1。

在這個階段,會執行類的構造函數,並且JVM也負責對類成員變量進行初始化賦值。例如:

public int b = 2;

這個時候b會被賦值2。

類加載的最終產品是位於堆中的Class對象,封裝了類在方法區內的數據結構,並向java程序員提供了訪問方法區內數據結構的接口。


好了,類的初始化過程我們已經研究明白了,下面我們來分析下陽哥的題目。

題目分析

我們先從程序的入口開始看:

public static void main(String[] args) {
    new Main("init");
}

這裏做了什麼呢?new了一個Main類的對象,往Main的構造函數裏傳了一個初始化的字符串 “init”。那麼對應到類的初始化過程,我們看看,實際會發生什麼?

  1. 初始化Main類的所有靜態成員變量(加載,驗證,一直到準備階段):
    給int類型的靜態成員變量賦值爲0,給引用類型的靜態成員變量賦值爲null,也就是說, k=0,i=0,n=0,j=0,t1=null,t2=null
  2. 按順序依次給靜態成員變量賦值:
    k賦值爲0
    t1=new Main(“t1”) 注意,這裏又進行了一次new Main(),但是這次因爲類已經初始化過了,過了準備階段,已經有了默認值,所以就直接給各個靜態成員變量賦實際值。因爲 j 是成員變量,非靜態變量,他是在對象實例化之前初始化的,所以先直接執行了方法print(“j”)
public static int print(String str) {
        System.out.println((++k) + ": " + str + " i=" + i + " n=" + n);
        ++n;
        return ++i;
    }

第一行輸出結果:
在這裏插入圖片描述
此時,k=1,n=1,i=1。執行完此方法後,開始實例化t1對象,執行構造函數:

public Main(String str) {
        System.out.println((++k) + ": " + str + " i=" + i + " n=" + n);
        ++i;
        ++n;
    }

第二行輸出:
在這裏插入圖片描述
至此static Main t1 = new Main(“t1”);完成了,此時 k=2,i=2,n=2。

接着到了賦值public static Main t2 = new Main(“t2”);的時候了,它也和t1類似。所以第三行第四行會輸出下面內容:
在這裏插入圖片描述
執行完t2的構造函數,k=4,i=4,n=4。
下面開始繼續給靜態成員 i 變量再次賦值:public static int i = print(“i”);
輸出結果:
在這裏插入圖片描述
執行完,k=5,i=5,n=5。
再往下:給靜態成員變量n再次賦值: public static int n = 99;,不輸出結果,n=99。
再往下,靜態成員變量全部完成了賦值,開始執行靜態代碼塊:

 static {
        print("靜態塊");
    }

執行結果:
在這裏插入圖片描述
執行完,k=6,i=6,n=100。

最後,Main的全部成員變量賦值完畢,開始初始化入口對象 new Main(“init”),它的套路也是跟t1一樣,先初始化成員變量,再執行構造函數,輸出:
在這裏插入圖片描述
執行完:k=8,i=8,n=102。

完整的執行結果如下:
在這裏插入圖片描述

呼~ 我長舒了一口氣,爲了解這道題可是花費了我不少時間啊!幸虧好好複習一遍類加載的知識把它們都搞懂了。

哎,又到了凌晨1點多了,估計這會陽哥睡了吧?完了,這下不得丟人丟到外太空去?這麼着吧,我就說我付錢看到小偷了,當時我爲了見義勇爲只能犧牲小我了,哎,誰讓咱們是社會的三好青年呢?優秀的讓人沒辦法啊~

看來只能下次再跟陽哥吹噓了,哈哈哈~

看着窗前的夜色,不禁詩興大發,決定吟詩一首:

唯有夜共語,
更無人通言。
寂寥如是斯,
我心獨自燃。

匿了匿了…


創作不易,如果您喜歡這篇文章的話,請你點贊支持一下作者好嗎?您的支持是我創作的源泉哦!如果您有什麼好的建議或者意見也歡迎在評論區跟我討論一下的哦。喜歡Java,熱衷學習的小夥伴可以加我微信: xia_qing2012,大家一起學習進步,成爲大佬!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章