如何理解符號(hào)引用和直接引用?
我們知道在 JVM 中類加載總共使用 5 步組成的,而類的生命周期總共有 7 個(gè)階段,如下圖所示:
其中每步的含義如下:
1.加載
加載(Loading)階段是整個(gè)“類加載”(Class Loading)過程中的一個(gè)階段,它和類加載 Class Loading 是不同的,一個(gè)是加載 Loading 另一個(gè)是類加載 Class Loading,所以不要把二者搞混了。
在加載 Loading 階段,Java 虛擬機(jī)需要完成以下 3 件事:
- 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。
- 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
- 在內(nèi)存中生成一個(gè)代表這個(gè)類的 java.lang.Class 對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。
2.驗(yàn)證
驗(yàn)證是連接階段的第一步,這一階段的目的是確保 Class 文件的字節(jié)流中包含的信息符合《Java 虛擬機(jī)規(guī)范》的全部約束要求,保證這些信 息被當(dāng)作代碼運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。
驗(yàn)證選項(xiàng):
- 文件格式驗(yàn)證
- 字節(jié)碼驗(yàn)證
- 符號(hào)引用驗(yàn)證...
3.準(zhǔn)備
準(zhǔn)備階段是正式為類中定義的變量(即靜態(tài)變量,被 static 修飾的變量)分配內(nèi)存并設(shè)置類變量初始值的階段。
比如此時(shí)有這樣一行代碼:
public static int value = 123;
它是初始化 value 的 int 值為 0,而非 123。
4.解析
解析階段是 Java 虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程,也就是初始化常量的過程。
也就是說這個(gè)階段會(huì)涉及到以下三個(gè)概念:
- 符號(hào)引用:類文件中的一種抽象引用方式,它并不涉及具體的內(nèi)存地址或?qū)ο髮?shí)例。符號(hào)引用包括了三個(gè)方面的信息:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。這些信息足夠唯一地確定一個(gè)類、字段或者方法,但在類被加載到 JVM 之前,并沒有與實(shí)際的內(nèi)存布局關(guān)聯(lián)。
- 直接引用:一種可以直接指向目標(biāo)對(duì)象、類、字段或者方法在 JVM 內(nèi)存中的物理位置的引用方式,例如指針、偏移量等。一旦有了直接引用,就可以直接訪問目標(biāo)實(shí)體,而無需再經(jīng)過其他查找過程。
- 替換過程:當(dāng) JVM 在解析階段需要對(duì)某個(gè)符號(hào)引用進(jìn)行解析時(shí),會(huì)根據(jù)類加載的結(jié)果生成對(duì)應(yīng)的直接引用。比如,當(dāng)一個(gè)類引用了另一個(gè)類的方法或字段時(shí),解析階段會(huì)確保被引用的目標(biāo)類已經(jīng)被加載,并計(jì)算出被引用方法或字段在內(nèi)存中的準(zhǔn)確位置,然后用這個(gè)位置信息替換掉原來的符號(hào)引用。
5.初始化
初始化階段,Java 虛擬機(jī)真正開始執(zhí)行類中編寫的 Java 程序代碼,將主導(dǎo)權(quán)移交給應(yīng)用程序。初始化階段就是執(zhí)行類構(gòu)造器方法的過程,當(dāng)然初始化階段也會(huì)執(zhí)行靜態(tài)初始化塊和靜態(tài)字段的初始化賦值的操作。
那么問題來了,以上步驟中在進(jìn)行【解析】階段時(shí)有兩個(gè)比較難理解的定義【直接引用】和【符號(hào)引用】,那么如何通俗易懂的理解二者的概念呢?
符號(hào)引用 VS 直接引用
這里通俗易懂的理解一下符號(hào)引用和直接引用:
- 符號(hào)引用:想象一下你去圖書館找一本書,但你沒有具體的書架位置,只有書名和作者,這是書名和作者就像是符號(hào)引用,你并不知道它在圖書館的哪個(gè)位置?你只知道書名和作者信息。
- 直接引用:之后你去了借閱臺(tái)或者目錄索引處查找這本書的具體位置,比如在第 3 層的 A 區(qū) 12 排 5 列,你可以直接走到這個(gè)位置找到書。這個(gè)具體的位置信息就像直接引用,它是一個(gè)可以直接定位到實(shí)體的指針或句柄。
也就是在【解析】步驟中,其實(shí)是將以字符串形式存在的,描述了類、接口、字段或方法的名稱,以及可能包含的其他關(guān)于被引用項(xiàng)的信息,轉(zhuǎn)換成實(shí)際內(nèi)存對(duì)象的過程。直接引用是實(shí)際的內(nèi)存地址或偏移量,使用它可以讓 JVM 能夠快速地訪問對(duì)象、方法或字段。