如果你點開了本篇文章,那么恭喜你發現寶藏了!
博主接下來將會更新整個系列的 《探索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的常量,
符號參考則屬于編譯原理方面的概念,包含三類:
- 類和介面的全限定名,如index=4存放的是CONSTANT_Class_info結構,指向的是類的全限定名
- 欄位的名稱和描述符,如index=3存放的是CONSTANT_Fieldref_info結構,指向的是欄位的名稱與描述符
- 方法的名稱和描述符,如index=17存放的是CONSTANT_Methodref_info結構,指向的是方法的名稱與描述符
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型別的決議作業,
intern方法做了什么
如果當前字串內容存在于字串常量池中,即使用 equas() 方法回傳ture,那直接回傳此字串在常量池的參考,如果不在字串常量池中,那么在常量池創建一個參考并且指向堆中已存在的字串,然后回傳常量池中的地址,是不是有點抽象,對著面試題再看一遍,
注意:該方法是有回傳值的,回傳的是常量池中的地址,為什么要強調呢?看面試題,
==與equals
==比較的是參考,即記憶體地址,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、決議
- 【s1 == s2: true】:因為s1、s2都指向字串常量池中同一字串:hello
- 【s2 == s3: false】:因為s2是指向字串常量池中的參考,s3是指向堆中的參考,自然不相等
- 【s3 == s4: false】:因為s3、s4是兩個不同的物件,自然不相等
- 【s2 == s3: false】:因為s3雖然呼叫了intern方法,但是未處理回傳值,所以s3依然是指向堆中的參考
- 【s2 == s3: true】:因為s3呼叫了intern方法,并且回傳給了s3,此時 s2、s3 都直接指向常量池的同一個字串,
好了,今天的文章就暫時先寫到這里了,如果本篇文章對你有幫助,想要繼續了解之后的更多JVM底層知識,請一定要點贊+關注,一鍵三連!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/188059.html
標籤:其他
上一篇:集合三兄弟List,Set,Map傻傻理不清?掌握訣竅面面俱到!
下一篇:Java基礎——面向物件和類
