探祕JVM串池

StringTable

String s1=“a”;
String s2=“b”;
String s3=“a”+”b”;
String s4=s1+s2;
String s5=“ab”;
String s6=s4.intern();

//問
System.out.println(s3==s4);
System,.out.println(s3==s5);
System.out.println(s3==s6);

String x2=new String(“c”)+new String(“d”);
String x1=“cd”;
X2.intern();
//問,如果調換最後兩行代碼的位置?如果是jdk1.6呢?
System.out.println(x1==x2);

這是幾道關於串池的題目,要想做對這幾道題目需要完全理解字符串在虛擬機中創建的過程以及運行的原理。首先我們介紹什麼是串池。
串池也就是StringTable,在JDK1.6中是方法區中運行時常量池的一部分([[Java內存區域]]),到JDK1.8時代,方法區被移到了本地內存,而串池留在了堆中。發生這一變化的原因是,只有在full gc的時候纔會觸發永久代的垃圾回收,而full gc發生在老年代空間不足時,觸發時間晚,但是StringTable用的非常頻繁,這就間接導致StringTable回收效率不高,這樣在minor gc時就可以觸發StringTable的垃圾回收,減輕了字符串對內存的佔用
串池的位置

串池和運行時常量池之間的關係

String s1=“a”;
String s2=“b”;
String s3=“ab”;

把上面這段代碼編譯成字節碼,然後再反編譯,得到下圖
在這裏插入圖片描述
當程序開始運行時,字節碼中的常量池被加載到了方法區中的運行時常量池,該區域中就存有字符串a, b, ab,但還沒有變成java字符串對象,此時串池是空的。
當執行到String s1=“a”時,觀察字節碼反編譯的結果,ldc #2 的含義是將運行時常量池中的第二個對象(也就是字符串a)變成“a”字符串對象,並會在StringTable中尋找是否存在“a”(StringTable個人理解是一個HashSet的結構,初始時有一個固定的大小,且不可擴容),如果沒有就將其存放到StringTable中。

字符串變量的拼接

String s1=“a”;
String s2=“b”;
String s3=“ab”;
String s4=s1+s2;

同樣的,我們得到反編譯這段代碼的結果
在這裏插入圖片描述
可以看到s1+s2這個動作實際上是先創建了一個StringBuilder對象,然後通過invokespecial調用了初始化函數(也就是構造函數),然後使用invokevirtual調用了StringBuilder類中的append函數,最後調用toString()函數,總結起來,s4=s1+s2等同於 StringBuilder().append(“a”).append(“b”).toString()。
在這裏插入圖片描述
我們可以看到,toString()方法返回了一個新的字符串對象,該對象創建於堆中。因此s3!=s4.

編譯期優化

String s1=“a”;
String s2=“b”;
String s3=“ab”;
String s4=s1+s2;
String s5=“a”+“b”;

同樣經過反編譯,我們只關注最後一行代碼的反編譯結果
在這裏插入圖片描述
結果是從常量池中直接尋找“ab”,這說明編譯器已經對這行代碼進行了優化。因此s3==s5.

intern()方法

Strng s=new String(“a”)+new String(“b”);

經過前面的描述,在串池中會存有“a”,“b”,同時堆中會有new String(“a”), new String(“b”), new String(“ab”)三個對象,那我們是否可以主動將字符串“ab”添加到串池中呢?可以調用intern()方法。
intern()方法的實現細節在jdk1.8和jdk1.6中是有差別的,首先介紹jdk1.8中的intern()方法。

  • jdk1.8 :該方法嘗試將字符串放入到串池中,如果串池中已經存在則不會存入串池,如果沒有則放入到串池,最後會把串池中的對象返回。如下圖,如果沒有第一行代碼,則運行的結果是true,true;如果有第一行代碼,則結果是true,false
String x=“ab”;
String s=new String(“a”)+new String(“b”);
String s2=s.intern();
System.out.println(s2==“ab”);
System.out.println(s==“ab”);
  • jdk1.6 :該方法嘗試將字符串放入串池,如果有則不放入,如果沒有則會把此對象複製一份,放入串池,最後會把串池中的對象返回。同樣的代碼,如果沒有第一行,運行結果是true,false,因爲s仍然在堆中,只是複製了一份“ab”添加到了串池。

總結

  • 常量池中的字符串僅是符號,第一次用到時才變爲對象
  • 利用串池的機制,可以避免重複創建字符串對象
  • 禮服串變量的拼接原理是StringBuilder
  • 字符串常量拼接的原理是編譯期優化
  • 可以使用intern方法,主動將串池中還沒有的字符串對象放入串池
    • 1.8將這個字符串對象嘗試放入串池,如果有則不放入,如果沒有則移入串池,最後把串池中的對象返回
    • 1.6將這個字符串對象嘗試放入串池,如果有則不放入,如果沒有則複製一份然後將副本移入串池,最後把串池中的對象返回
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章