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