如果你點開了本篇文章,那麼恭喜你發現寶藏了!
博主接下來將會更新整個系列的 《探索JVM的底層祕密》 文章,為大家完整的剖析JVM的底層原理。
作者最近在優化JVM記憶體模型這方面的內容,發現自己對於Java中的常數池的理解有點零碎,做個總結,於是就有了這篇文章。本篇文章所有知識點基於jdk8。
jdk6、jdk7不適用,如果有疑問,歡迎在評論區留言。廢話不多說,直接上程式碼。
比如你寫了一段這樣的Java程式碼,JVM是如何處理的呢?
1、Java程式碼
public class StringTest2 {
String name = "子牙";
public static void main(String[] args) {
StringTest2 obj = new StringTest2();
}
}
2、class檔案之常數池(圖1)
3、預設構造方法位元組碼(圖2)
1、class檔案中的常數池
這個常數池中主要存放兩大類常數:字面量、符號參照。
字面量即文字字串,如index=10的Code、index=11的LineNumberTable……還有宣告為final的常數。
符號參照則屬於編譯原理方面的概念,包含三類:
2、執行時常數池
方法區的一部分。我們常說的常數池,就是指這一塊區域:方法區中的執行時常數池。
那資料是何時存入這塊區域的呢?是在類載入階段,類載入器子系統會將class檔案中的常數池中的資料封裝成相應的CONSTANT_*結構存入進去。
這裡重點說下index=2的資料項。index=2對應的資料結構是CONSTANT_String,但是在類載入階段,index=2儲存的資料結構卻是JVM_CONSTANT_UnresolvedString,為什麼會這樣呢?因為載入類的時候,還沒有解析字串字面量,即沒有將符號參照轉為直接參照。那何時解析的呢?執行引擎執行ldc指令的時候。不懂?往後看。
3、全域性字串常數池
這個常數池在JVM層面就是一個StringTable,只儲存對java.lang.String範例的參照,而不儲存String物件的內容。
一般我們說一個字串進入了字串常數池其實是說在這個StringTable中儲存了對它的參照,反之,如果說沒有在其中就是說StringTable中沒有對它的參照。
基礎知識講完了,咱們來實戰一下。就以JVM處理上面貼出的程式碼為例,給童鞋們分享一下執行流程:
1、呼叫javac命令編譯java檔案生成class檔案,class檔案中的常數池(圖1)
2、呼叫java命令開始執行這個class檔案,類載入器子系統將class檔案載入進記憶體,並將常數池中的資料封裝成相應的CONSTANT_*結構存入執行時常數池。這時候常數池中index=2的位置存放的是JVM_CONSTANT_UnresolvedString而不是JVM_CONSTANT_String_info
3、執行引擎執行StringTest2的預設建構函式,即圖3。大家是否注意到ldc指令,那這個指令做了什麼呢?執行引擎執行ldc指令時,會根據ldc後面的運算元去執行時常數池中查詢對應的值,並判斷是否已完成解析,如果已解析就直接返回字串在堆中的參照,即記憶體地址。如果沒有解析進去解析,那如何解析呢?
4、根據JVM_CONSTANT_UnresolvedString中存放的index去執行時常數池中查詢CONSTANT_Utf8_info結構,這個結構存放了字串的具體內容及字串長度。然後判斷字串常數池中是否有這個字串的參照,如果有就直接返回,如果沒有就去堆中建立一個對應內容的String物件,並將參照儲存在字串常數池中。這樣就完成了String型別的解析工作。
如果當前字串內容存在於字串常數池中,即使用 equas() 方法返回ture,那直接返回此字串在常數池的參照。如果不在字串常數池中,那麼在常數池建立一個參照並且指向堆中已存在的字串,然後返回常數池中的地址。是不是有點抽象,對著面試題再看一遍。
注意:該方法是有返回值的,返回的是常數池中的地址。為什麼要強調呢?看面試題。
==比較的是參照,即記憶體地址。equals比較的是兩個物件的內容。
如果你對Java中的常數池理解得不是很透徹,這道面試題你還真不一定能答上來。就算告訴了你答案你可能也會一臉懵逼。那這篇文章你已經看到這裡了,我希望你已明瞭。建議同學們先不要看答案以及我的解析,先自己回答一下,然後給出自己的分析,再看答案。
1、上程式碼
public class StringTest1 {
public static void main(String[] args) {
String s1 = "子牙真帥";
String s2 = "子牙真帥";
String a = "子牙";
String s3 = new String(a + "真帥");
String s4 = new String(a + "真帥");
System.out.println("s1 == s2: " + (s1 == s2));
System.out.println("s2 == s3: " + (s2 == s3));
System.out.println("s3 == s4: " + (s3 == s4));
s3.intern();
System.out.println("s2 == s3: " + (s2 == s3));
s3 = s3.intern();
System.out.println("s2 == s3: " + (s2 == s3));
}
}
2、返回結果
s1 == s2: true
s2 == s3: false
s3 == s4: false
s2 == s3: false
s2 == s3: true
3、解析
好了,今天的文章就暫時先寫到這裡了,如果本篇文章對你有幫助,想要繼續瞭解之後的更多JVM底層知識,請一定要點贊+關注,一鍵三連!