Java8 中String 的變化

jopen 10年前發布 | 76K 次閱讀 Java8 Java開發

String 是最常使用的類型之一,Java8 (包括Java7) 對于String的相關實現做出了重大的修改。String的接口并沒有改變,對于編寫代碼的人來說是看不到區別的,不過卻會很大程序上的影響JVM GC,內存占用,以及程序性能等。如果想對JVM 做性能分析和調優,編寫在Java8 中更優化的代碼,就有必要了解Java8 中String 實現的細節。

1. 不再共享char[] 數組

這個改動實際是從1.7.0_06開始發生的。String 內部是保存了一個char[],用來記錄String 中的字符串。在之前的實現中,String 有四個Field: 

char[] value;
int offset;
int count;
int hash;
由于String 的不變行,我們可以在不同的String 之間,共用一個char[] value,每個String 有自己獨立的offset 和 count. 這樣,對于subString, trim(), split() 等這樣的操作,創建新的String對象的時候,仍然使用原來那個String 的value,從而可以做到O(1) 的時間復雜度。

這種方法的缺點是對內存回收有嚴重的影響。當從很長的字符串中截取一個很短的字符串時,雖然需要使用的只是很少的字符,但是卻引用了一個很大的char[] 數組,不能釋放。

新的String 實現,去掉了String 中的offset 和 count 這兩個field。

char[] value;
int hash;

一眼看上去對于每個String 省掉了兩個int 共8 個字節的開銷,當然這點開銷是微不足道的。最重要的影響,是String 之間無法共享char [] value 了。造成的后果是:subString, trim, split 等操作需要創建新的數組,并進行內存拷貝,時間復雜度變成O(n) ! 得到的好處則是char[] value 的生命周期變短,能夠及時釋放不需要的內存,改善內存占用和GC 性能 。

2. 常量池的變化

String 不變性的另外一個可以優化的地方,就是常量池(String pool),對于短小的String對象,保存到String pool中,如果遇到同樣的String,則只要復用String pool中的實例就可以了 。編譯器只能處理常量的String,將其放到String pool中,如果需要自己操作,則可以用String.intern()方法。

在Java6 中,String Pool 是放在PermGen 區域的。PermGen 區域的問題是一般容量比較小,以及GC 困難。

在Java7 中,String Pool 被移到了Heap 中,一方面可以使用更大的String Pool了(通過XX:StringTableSize設置),另外一方面,也為Java8 徹底移除PermGen 做準備。由于移到了Heap 中,如果String pool 中的一個String,沒有被任何實例所引用,是會被GC 回收的。

從Java7 update40 之后,String Pool 的默認大小由1009 提高到了60013。這是一個相當大的數字。

3. 字符串去重

String deduplication 是Java8u20 之后引入的一個特性,這個特性完全是JVM 層面的。要使用這個特性,必須使用G1 garbage collector,通過設置參數:

-XX:+UseStringDeduplication -XX:+PrintStringDeduplicationStatistics
開啟字符串去重功能。

開啟了這個功能之后,JVM 在GC的時候,會順便通過hashCode和value,來比較兩個字符串是否相等。如果相等的話,就會把其中一個字符串的value 字段,指向另外一個字符串,這樣同樣的字符串最后只會使用一個char[] value。

原文鏈接: http://www.dongliu.net/post/5778384575528960

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!