jvm串池StringTable

161 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

(6)StringTable

面试题

public class StringTableStudy {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
    }
}
​

常量池中的信息,都会被加载到运行时常量池中,但这是a b ab 仅是常量池中的符号,还没有成为java字符串

0: ldc          #2          // String a
2: astore_1
3: ldc          #3          // String b
5: astore_2
6: ldc          #4          // String ab
8: astore_3
9: return
  • 当执行到 ldc #2 时,会把符号 a 变为 “a” 字符串对象,并放入串池中(hashtable结构 不可扩容)
  • 当执行到 ldc #3 时,会把符号 b 变为 “b” 字符串对象,并放入串池中
  • 当执行到 ldc #4 时,会把符号 ab 变为 “ab” 字符串对象,并放入串池中
  • 最终StringTable [“a”, “b”, “ab”]

注意:字符串对象的创建都是懒惰的,只有当运行到那一行字符串且在串池中不存在的时候(如 ldc #2) 时,该字符串才会被创建并放入串池中。

使用拼接字符串变量对象创建字符串的过程

public class StringTableStudy {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
        //拼接字符串对象来创建新的字符串
        String ab2 = a+b;
    }
}
​

反编译后的结果

 Code:
 stack=2, locals=5, args_size=1
 0: ldc #2 // String a
 2: astore_1
 3: ldc #3 // String b
 5: astore_2
 6: ldc #4 // String 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/Str
ing;
 27: astore 4
 29: return

通过拼接的方式来创建字符串的过程是:StringBuilder().append(“a”).append(“b”).toString()

最后的toString方法的返回值是一个新的字符串,但字符串的值和拼接的字符串一致,但是两个不同的字符 串,一个存在于串池之中,一个存在于堆内存之中

String ab = "ab";
String ab2 = a+b;
//结果为false,因为ab是存在于串池之中,ab2是由StringBuffer的toString方法所返回的一个对象,存在于堆内存之中
System.out.println(ab == ab2);

使用拼接字符串常量对象的方法创建字符串

public class StringTableStudy {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        String ab = "ab";
        String ab2 = a+b;
        //使用拼接字符串的方法创建字符串
        String ab3 = "a" + "b";
    }
}

反编译后的结果

 Code:
 stack=2, locals=6, args_size=1
 0: ldc #2 // String a
 2: astore_1
 3: ldc #3 // String b
 5: astore_2
 6: ldc #4 // String 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/Str
ing;
 27: astore 4
 //ab3初始化时直接从串池中获取字符串
 29: ldc #4 // String ab
 31: astore 5
 33: return
  • 使用拼接字符串常量的方法来创建新的字符串时,因为内容是常量,javac在编译期会进行优化,结果已 在编译期确定为ab,而创建ab的时候已经在串池中放入了“ab”,所以ab3直接从串池中获取值,所以 进行的操作和 ab = “ab” 一致。
  • 使用拼接字符串变量的方法来创建新的字符串时,因为内容是变量,只能在运行期确定它的值,所以需要 使用StringBuilder来创建

intern方法

intern方法 1.8

调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中

  • 如果串池中没有该字符串对象,则放入成功
  • 如果有该字符串对象,则放入失败

无论放入是否成功,都会返回串池中的字符串对象

注意:此时如果调用intern方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个 对象

例1

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);
    }
}

例2

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方法成功与否,串池中的字符串对象和堆内存中的字符串对象都不是同一个对象

(7) StringTable 特性

  • 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是编译器优化
  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中
  • 注意:无论是串池还是堆里面的字符串,都是对象

(8)StringTable 位置

  • jdk6以前,字符串常量池存放在永久代
  • jdk7之后,字符串常量池的位置调整到java堆中
  • jdk8加入元空间,字符串常量池在堆内

(9)StringTable 垃圾回收

StringTable在内存紧张时,会发生垃圾回收

(10)StringTable 性能调优

因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串 池所需要的时间

-XX:StringTableSize=xxxx

考虑是否需要将字符串对象入池

可以通过intern方法减少重复入池