文章目錄
- 視訊資源位址
- 筆記資源位址
- 我的筆記
- 27.stringtable·面試題 + 28.常量池與串池的關系
- 29.字元串變量拼接
- 30.編譯器優化
- 31.字元串延遲加載
- 32-33.stringtable_intern_1.8與1.6
- 34. string_table 面試題
- 35-36. string_table 位置
- 37. string_table 垃圾回收
- 38-40. string_table 性能調優
視訊資源位址
B站 https://www.bilibili.com/video/av70549061
筆記資源位址
https://nyimac.gitee.io/
我的筆記
27.stringtable·面試題 + 28.常量池與串池的關系
首先寫一段聲明字元串的代碼,通過反編譯檢視他的常量池的樣子。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5COxUzM0UGMklTOlRjY1I2NzYzX2QDNwkTMxIzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
關鍵指令:javap -v class位址
看位元組碼的内容,這一行:去常量池2位置中加載資訊。 然後存入局部變量1中。如下圖局部變量表。
以此類推,下圖中三個變量都是這樣加載進來,并指派在局部變量表的。
常量池是存在位元組碼檔案中的,上面這個例子已經證明了,但是這個靜态的,當它運作起來之後,就會被加載到運作時常量池。如下圖這些都會被加載到運作時常量池:
注意:
當位元組碼執行到ldc的時候,才會将符号跟java對象聯系起來。
此時,會去檢視Stringtable,。也就是所謂的串池,有沒有包含a,如果不包含就加入進去。
這個行為是一個懶惰的行為,代碼中用到這個對象,才會去串池中查找或者添加。
原文筆記:
29.字元串變量拼接
反編譯之後如下圖所示。相當于執行了注釋中的操作。
問題:
答案是fasle。因為s3在串池裡,s4在堆裡。 tostring方法的本質是調用了new string(“ab”) 方法。他倆的位置不一樣,是兩個對象。
30.編譯器優化
建立一個string s5 = “a” + “b”; 然後反編譯。
他是直接去常量池中加載的#4這個常量。并且存入了5号局部變量表。
是以s3和s5的變量都是存儲的串池中的同一個字元串常量,是以他倆是相等的。
“a” + “b” 是 javac在編譯期的優化。 這倆都是常量,相加不會變了,結果在編譯期間已經确定為“ab”。
跟s4不一樣,s4的生成是兩個變量相加,需要動态拼接。就到了堆中了。
31.字元串延遲加載
上面這個例子。隻會在串池中建立10個對象,再下面繼續列印,就是用的串池中的了。
32-33.stringtable_intern_1.8與1.6
調用字元串對象的intern方法,會将該字元串對象嘗試放入到串池中.
- 如果串池中沒有該字元串對象,則放入成功
-
如果有該字元串對象,則放入失敗
無論放入是否成功,都會傳回串池中的字元串對象
注意:此時如果調用intern方法成功,堆記憶體與串池中的字元串對象是同一個對象;如果失敗,則不是同一個對象。
public class Main {
public static void main(String[] args) {
//"a" "b" 被放入串池中,str則存在于堆記憶體之中
String str = new String("a") + new String("b");
//調用str的intern方法,這時串池中沒有"ab",則會将該字元串對象放入到串池中,此時堆記憶體與串池中的"ab"是同一個對象
String st2 = str.intern();
//給str3指派,因為此時串池中已有"ab",則直接将串池中的内容傳回
String str3 = "ab";
//因為堆記憶體與串池中的"ab"是同一個對象,是以以下兩條語句列印的都為true
System.out.println(str == st2);
System.out.println(str == str3);
}
}
public class Main {
public static void main(String[] args) {
//此處建立字元串對象"ab",因為串池中還沒有"ab",是以将其放入串池中
String str3 = "ab";
//"a" "b" 被放入串池中,str則存在于堆記憶體之中
String str = new String("a") + new String("b");
//此時因為在建立str3時,"ab"已存在與串池中,是以放入失敗,但是會傳回串池中的"ab"
String str2 = str.intern();
//false
System.out.println(str == str2);
//false
System.out.println(str == str3);
//true
System.out.println(str2 == str3);
}
}
intern方法 1.6 :
調用字元串對象的intern方法,會将該字元串對象嘗試放入到串池中:
- 如果串池中沒有該字元串對象,會将該字元串對象複制一份,再放入到串池中
- 如果有該字元串對象,則放入失敗
無論放入是否成功,都會傳回串池中的字元串對象。
注意:此時無論調用intern方法成功與否,串池中的字元串對象和堆記憶體中的字元串對象都不是同一個對象
34. string_table 面試題
19與20行調換位置之前: 最後的答案是true,因為jdk1.8中,x2會從堆中也把同一對象放到串池中,是以x1=“cd”,直接從串池拿的對象。
19與20行調換位置之後:false,x1的“cd”來自于串池,是建立的,x2來自于堆,把x2放入串池的操作,實際晚了一步。
jdk1.6,調換位置之前:false,因為1.6會從堆中複制一個對象去串池,實際上兩個對象不同。
jdk1.6,調換位置之後:false,一個是堆中的,一個是串池的,2個對象不同。intern沒起到作用。
35-36. string_table 位置
1.6 :string table 是常量池的一部分,跟常量池一起存在永久代中,永久代的記憶體回收效率很低,隻有在full gc的時候才會觸發垃圾回收機制,full gc隻在老年代空間不足時候才會觸發,觸發的時機不對,但是stringtable用的是十分頻繁的,這種垃圾回收頻顯然不行。1.7之後,stringtable就轉移到了堆中,年輕代的垃圾回收機制很頻繁,是以性能好。驗證程式如下:
1.6版本報錯:
1.8版本報錯:并沒有出現heap關鍵詞
jvm原文:如果百分之98的時間用來垃圾回收,就說明癌症晚期。就會報上面的錯,也說明堆空間不足了。如果把這個功能 -XX:-xxxx,就可以出現堆記憶體溢出了。
37. string_table 垃圾回收
stringtable在堆中,雖然是字元串常量,那麼也得被垃圾回收。
将jvm做如下設定:
Xmx:規定堆記憶體的大小
-XX:+pringtStringTableStatistics 答應串池的統計資訊
後面兩個參數:列印垃圾回收的相關資訊
運作一段代碼,代碼沒有做任何事情。
列印内容如下:
上述是堆的資訊:新生代,老年代,元空間分别的大小喝使用情況。
符号統計:類的位元組碼裡那些類名、方法名、變量名,也是需要讀入到記憶體中,以查表的方式去讀出來。也是屬于常量池的一部分。
stringtable的統計資訊:底層的實作是hashtable,是數組+連結清單的結構。
主要看前三個number,number of literal 是說明的字元串常量的統計。因為jvm中本身是有很多字元串常量的。
這時候,main函數中增加字元串常量往串池中intern的操作。
運作結果如下:增加了100個對象
如果增加的常量變成1w個,那麼10m的堆空間肯定不夠,就會觸發gc了。
實際上隻存進去7000多(含有本身的1754)。過程中肯定因為記憶體配置設定失敗觸發了gc。無用的字元串常量被清掉了。
38-40. string_table 性能調優
stringtable底層是一個hash表,性能跟他的大小相關。
tips:垃圾回收隻有在記憶體緊張時才會觸發。
檔案中有50w行關鍵字資料。流讀入之後,放入串池。
首先,進行jvm的參數設定,
進行入池:0.4秒,速度是非常快的。
然後把stringtablesize 這個配置去掉,性能明顯降低了。這個參數是調節預設桶大小的,大了,性能會高。
建議:如果你的系統中字元串常量很多,建議調大stringtablesize,必須大于1009,預設是60000.減少has沖突,提升效率。
調優:對于位址等資訊,拆分省市縣區和具體位址,然後用串池的方法載入到内容,比一次性全部載入要節省空間。
demo實驗,證明調優:
不加line.intern():