顾名思义,StringTable 就是存储字符串常量的一个池子。JVM 指令执行时读取到字面量想作为字符串时会先去 StringTable 中查找,没有就创建出来并放入 StringTable 中,有就直接指向该对象
其底层数据结构是一个不会扩容的哈希表加链表(默认桶长 60013 );key 、value 都是该字符串对象
两种特殊情况
- 字符串变量拼接时(只要其中一个是变量)不会去池子中查找,此时原理为 StringBuilder
- 字符串常量间拼接时,会进行编译器优化,直接去池子中找拼接完成后的字符串
public class demo01 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuiler().append("a").append("b").toString()
String s5 = "a" + "b"; // 就相当于是 "ab"
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
}
}
// 该方法的 JVM 指令
0: ldc #2 // 取 a 创建字符串对象,放入 StringTable 中,并让 s1 指向它,此时 StringTable 为 ["a"]
2: astore_1
3: ldc #3 // 取 b 创建字符串对象,放入 StringTable 中,并让 s2 指向它,此时 StringTable 为 ["a","b"]
5: astore_2
6: ldc #4 // 取 ab 创建字符串对象,放入 StringTable 中,并让 s3 指向它,此时 StringTable 为 ["a","b","ab"]
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String
27: astore 4
29: ldc #4 // 编译器自动将 "a" + "b" 转化为 "ab" 。取 ab 发现 StringTable 中有 "ab" 对象,让 s5 直接指向它
31: astore 5
为什么 StringTable 要从方法区调整到堆内存中?
- 程序中经常会使用字符串字面量,过多的字符串字面量会导致方法区中大部分的空间用来存放字符串对象,本身就不合理
- 再者方法区应该是一个几乎不进行 GC 的区域,而 StringTable 是一块很活跃的区域,可能会导致方法区频繁的触发 Full GC ,影响性能
- 将 StringTable 放在堆内存中,随着 Minor GC 一起回收在物理上和逻辑上都很合理
insert 方法
尝试将字符串变量放入 StringTable 中,如果有内容相同的字符串则不会放入,没有则放入。返回结果总是指向 StringTable 中的字符串,通过下例辅助理解
public static void test01(){
String s = new String("ab"); // 创建字符串 "ab" 放入 StringTable ,再在堆中创建字符串 "ab" ,让 s 指向堆中 "ab"
String s_intern = s.intern(); // 尝试将堆中 "ab" 放入 StringTable 中,放入失败。s_intern 指向 StringTable 中的 "ab"
System.out.println(s == s_intern); // false
}
public static void test02(){
String s = new String("a") + new String("b"); // 创建字符串 "a" 和 "b" 放入 StringTable 中,再在堆中创建字符串 "a" "b" "ab" ,让 s 指向堆中 "ab"
String s_intern = s.intern(); // 尝试将堆中 "ab" 放入 StringTable 中,放入成功。s_intern 指向该 "ab"
System.out.println(s == s_intern); // true 。此时 s 和 s_intern 指向的都是放入到 StringTable 中的 "ab"
}