在jvm启动时,常量池中的内容都会加载到运行时常量池中,但是此时a,b,ab都还只是一个符号,而不是字符串对象。只有当执行到具体的指令,如0: ldc #2才会创建字符串对象"a"。于此同时,jvm还会去String table[]中去找是否有"a"这个字符串,如果没有则将其加入String table[]。注:String table[]其实是hashtable 结构,不能扩容。
在java代码中新增s4,并反编译。
String s4 = s1 + s2;
反编译结果如下。
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
以上操作等同于。
new StringBuilder().append("a").append("b").toString() 1
其中toString()的方法实现方式是:new String("ab")。所以s3 == s4的结果为fasle.
System.out.println(s3 == s4); //false 1
接着我们在代码中新增s5.
String s5 = "a" + "b"; 1
反编译结果如下。
29: ldc #4 // String ab 31: astore 5
原来,javac编译时帮助我们进行了优化, 它认为“a”,“b”是常量,结果不可能会发生改变,于是结果直接在编译期确定为ab了。并且,由于"ab"在String table中已经存在,因此不会创建新的字符串对象了。
System.out.println(s3 == s4); //true 1
intern()方法可以把堆中的字符串对象放入串中,参考以下代码。
public class Demo1_23 { // String table["ab", "a", "b"] public static void main(String[] args) { String x = "ab"; String s = new String("a") + new String("b"); // 堆 new String("a") new String("b") new String("ab") String s2 = s.intern();//将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回 System.out.println( s2 == x); //true,s2与x都是串池中的对象 System.out.println( s == x ); //false,s是堆中的对象,与串池中的对象是不同的对象 } }
下面这种情况x2可以成功加入串池,因此结果为true。
String x2 = new String("c") + new String("d"); // new String("cd") x2.intern(); String x1 = "cd"; System.out.println(x1 == x2); //true
不过jdk1.6中调用intern()方法,会将字符串尝试放入串池,如果有则不会放入,如果没有则会复制一份放入串池,因此,串池中的对象与堆中的对象并不是同一个对象。上面同样的代码再jdk1.6中x1 == x2返回false。
串池的特点总结如下。
7.5 String table的位置
在jdk1.6,string table置于常量池,而常量池位于永久代的方法区中。永久代只有full gc触发时才会进行回收,这就导致string table的回收效率低。jdk1.7将string table移到了堆中。
7.6 String table的垃圾回收
参考以下代码配置参数并运行。
/** * 演示 StringTable 垃圾回收 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc */ public class Demo1_7 { public static void main(String[] args) throws InterruptedException { int i = 0; try { for (int j = 0; j < 100000; j++) { // j=100, j=10000 String.valueOf(j).intern(); i++; } } catch (Throwable e) { e.printStackTrace(); } finally { System.out.println(i); } } }
打印信息如下
[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->875K(9728K), 0.0028226 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 2536K->512K(2560K)] 2923K->958K(9728K), 0.0039494 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2560K->512K(2560K)] 3006K->1006K(9728K), 0.0020900 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] ... StringTable statistics: Number of buckets : 60013 = 480104 bytes, avg 8.000 Number of entries : 26231 = 629544 bytes, avg 24.000 Number of literals : 26231 = 1548152 bytes, avg 59.020 Total footprint : = 2657800 bytes Average bucket size : 0.437 Variance of bucket size : 0.418 Std. dev. of bucket size: 0.646 Maximum bucket size : 4