StringTable 筆記記錄
- 1. 常量池、運行時常量池與字符串常量池(StringTable)的關系
- 2. String str="a"放入字符串常量池的過程
- 3. 常見面試題
- 4. StringTable特性
- 5.StringTable的位置變更
- 5.1 為什么位置變換?
- 5.2 位置變更演示
- 6. StringTable垃圾回收
- 7. StringTable性能調優
- 7.1 考慮字符串是否入池(字符串常量池)
1. 常量池、運行時常量池與字符串常量池(StringTable)的關系
/*** StringTable[] 當變為字符串對象時,還會將符號當作key在StringTable中去找,看有沒有取值相同的key,* 如果沒有就放入,如果有就直接使用。* 也就是StringTable結構其實是一個哈希表。哈希表是長度固定的,不能進行擴容。如果沒有“a”字符串對象,則* 會放入串池。StringTable ["a"],執行完String s1="a"時就會放入串池中。下一行代碼類似的。**/
public class Demo {//常量池中的信息,都會被加載到運行時常量池中,這時a,b,ab,都是常量池中的符號,還沒有變為java字符串對象//當執行到該行代碼時://ldc #2 會把a符號變為 “a” 字符串對象//ldc #3 會把b符號變為 “b” 字符串對象//ldc #4 會把ab符號變為“ab” 字符串對象public static void main(String[] args) {//將“a”放入字符串池中是一個惰性的過程,當執行到該行代碼時,才會去檢查是否有。String s1="a";String s2="b";String s3="ab";}
}
使用javap -v class文件看具體細節。
這里能看到Constant poool;
常量池:.class 文件中的靜態數據,存儲字面量和符號引用。
運行時常量池:類加載后解析的常量池,支持動態修改。
字符串常量池:運行時常量池的子集,專門存儲字符串字面量。
2. String str="a"放入字符串常量池的過程
3. 常見面試題
String s1 = "a";String s2 = "b";String s3 = "ab";//new StringBuilder().append("a").append("b").toString;//StringBuilder的toString方法最終是new String("ab");String s4 = s1+s2; String s5="a"+"b";System.out.println(s3==s4);//false s3在字符串常量池中,s4在堆中,地址不一樣。System.out.println(s3==s5);//true 編譯器優化 實際上是"ab" 這里都可以利用javap -v *.class 查看字節碼
String s1 = “a”;
String s2 = “b”;
String s4 = s1+s2;的底層如下
4. StringTable特性
- 常量池中的字符串僅是符號,第一次用到時才變為對象。
- 利用串池的機制,避免重復創建字符串對象。
- 字符串變量拼接的原理是StrngBuilder(1.8)
- 字符串常量拼接的原理是編譯器優化 String s5=“a”+“b”;
- 可以使用intern方法,主動將串池中還沒有的字符串對象放入串池
//串池中StringTable [ "a","b"]//堆中 [ "a", "b","ab" ] 這里注意串池中沒有ab,因為這里是動態拼接的變量而不是常量。如果是String str="ab",則串池中存在。String s=new String("a")+new String("b");//想要把s的ab放入串池中調用 s.intern();即可。s.intern();String s2="ab";System.out.println(s==s2);
String x="ab";String s1=new String("a")+new String("b");String s2 = s1.intern();System.out.println(s2==x);//trueSystem.out.println(s1==x);//false x的ab已經放進去了,實際上s1.intern()放不進去了,所以s1和x不相等
這里注意JDK及7以后:
String s1=new String("a")+new String("b");String s2 = s1.intern();String x="ab";System.out.println(s2==x);//trueSystem.out.println(s1==x);//true
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);//false s3在常量池s4由StringBuilder拼接然后new String對象 在堆中System.out.println(s3==s5);//true s3編譯器優化 實際還是"ab"System.out.println(s3==s6);//trueString x2=new String("c")+new String("d");String x1="cd";x2.intern();System.out.println(x1==x2);//false x2放不進去常量池因為已經存在了,所以x2.intern()返回的是常量池中的對象
5.StringTable的位置變更
5.1 為什么位置變換?
5.2 位置變更演示
import java.util.ArrayList;
import java.util.List;/*** 演示StringTable位置* -Xmx10m* -XX:-UseGCOverheadLimit [寫+就是打開開關,-就是關閉。]** -XX:-UseGCOverheadLimit 詳解* 1. 作用* -XX:-UseGCOverheadLimit 是 JVM 的一個 故障保護機制開關,默認啟用(-XX:+UseGCOverheadLimit)。* 它的核心作用是:* 當 JVM 檢測到 GC 占用過多時間(超過 98%)但回收效果極差(釋放內存 < 2%)時,拋出 OutOfMemoryError: GC Overhead Limit Exceeded 錯誤,防止應用陷入無限 GC。* 2. 觸發條件* JVM 會在以下情況觸發該錯誤:* GC 時間占比 > 98%(如 100ms 里 98ms 在 GC)。* GC 后內存釋放 < 2%(幾乎沒回收空間)。* 持續超過 5 次 Full GC(不同 JVM 實現可能略有差異)。* 3. 關閉方式* 通過 -XX:-UseGCOverheadLimit 可禁用此機制,讓 JVM 繼續嘗試 GC,而非直接報錯。* 但需謹慎使用,可能讓應用卡死在 GC 中!*/
public class Demo {public static void main(String[] args) {List<String> list = new ArrayList<>();int i = 0;try {for (int j = 0; j < 260000; j++) {list.add(String.valueOf(j).intern());i++;}} catch (Throwable e) {e.printStackTrace();} finally {System.out.println(i);}}
}
看報錯也就知道了串池在堆空間。
6. StringTable垃圾回收
執行代碼前的字符串常量池統計
往池子里加了100個對象后
/*** 演示StringTable垃圾回收* -Xmx10m 堆空間設置10m* -XX:+PrintStringTableStatistics 打印字符串常量池的統計信息* -XX:+PrintGCDetails -verbose:gc 打印GC信息*/
public class Demo {public static void main(String[] args) {int i=0;try {for (int j=0;j<100;j++){String.valueOf(j).intern();i++;}} catch (Exception e) {e.printStackTrace();}finally {System.out.println( i);}}
}
后面改成10000個對象
public static void main(String[] args) {int i=0;try {for (int j=0;j<10000;j++){String.valueOf(j).intern();i++;}} catch (Exception e) {e.printStackTrace();}finally {System.out.println( i);}}
發生了GC
這里就證明了確實StringTable是會發生垃圾回收的。
7. StringTable性能調優
StringTable底層是哈希表。
這里是讀48w個單詞,-XX:StringTableSize=200000 【調整字符串常量池StringTable的大小】
這里就是動態的調整jvm參數發現執行的時間變化是很大的。
7.1 考慮字符串是否入池(字符串常量池)
for循環10次,將480w都存入list中。
沒有入池之前> 沒有入池占用到80%左右。
入池之后
占用30%多左右。
如果引用出現大量的重復字符串,可以讓字符串入池,來減少字符串對象個數,節約堆內存的使用。