StringTab 和intern()
根据几个实例来帮助我们理解StringTab在JVM中的运行方法
//JDK1.8
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
System.out.println(s3 == s5);//true
System.out.println(s3 == s6);//true
String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();
System.out.println(x1 == x2);//false
分析:
编译反编译java文件:
public class StingTab {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
}
}
javac -g StingTab.java
- ,
javap -v StingTab.class
//常量池
Constant pool:
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = String #25 // a
#3 = String #26 // b
#4 = String #27 // ab
#5 = Class #28 // com/wghcwc/gc/StingTab
#6 = Class #29 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/wghcwc/gc/StingTab;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 s1
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 s2
#21 = Utf8 s3
#22 = Utf8 SourceFile
#23 = Utf8 StingTab.java
#24 = NameAndType #7:#8 // "<init>":()V
#25 = Utf8 a
#26 = Utf8 b
#27 = Utf8 ab
#28 = Utf8 com/wghcwc/gc/StingTab
#29 = Utf8 java/lang/Object
......
//构造方法..略
....
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
//去常量池 加载#2位置对应符号------> a ,将a符号变成字符串对象
0: ldc #2 // String a
//把上一步加载的 a 存入局部变量表slot变黄为 1的位置 args s1
对应代码 String s1 = "a";
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: return
//行号
LineNumberTable:
line 10: 0
line 11: 3
line 12: 6
line 13: 9
//局部变量表
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
3 7 1 s1 Ljava/lang/String;
6 4 2 s2 Ljava/lang/String;
9 1 3 s3 Ljava/lang/String;
}
根据上一篇,JVM内存结构
我们知道,字节码的中的常量池(Constant pool)会在类加载后加载到运行时常量池.但是,在加载到运行时常量池时,字节码常量池中的字符串符号并没有成为java 字符串对象,直到运行到相应代码时,才会把对应的字符串符号转换成java对象,并放入StringTab,所以上面代码执行完毕,StringTab 中将包含{“a”,“b”,“ab”}
接下来 添加一句String s4 = "a" + "b";
看字节码
public class StingTab {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b";
}
}
javac -g StingTab.java
- ,
javap -v StingTab.class
stack=1, 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: ldc #4 // String ab
11: astore 4
+++++++++++++++++++++
13: return
可以看到
String s3 = "ab";
String s4 = "a" + "b";
所对应的字节码是一样的,值引用了同一个地址#4的数据
所以可以毫不费力的得出 s3=s4
我们继续分析 ,编译反编译如下代码
public class StingTab {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = "a" + "b";
String s5 = s1 + s2;
}
}
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: ldc #4 // String ab
11: astore 4
+++++++++++++++++++++++++++++
创建/StringBuilder 实例
13: new #5 // class java/lang/StringBuilder
//复制一份StringBuilder 实例
16: dup
//调用构造函数,此时会消耗一份StringBuilder 实例,也是上一步的原因
17: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
//参数 a
20: aload_1
//调用append
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_2
25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore 5
33: return
可以看到String s5
所对应的字节码多了很多,根据后面的注释我们不难猜出是根据StringBuilder实现的两个字符串对象相加…
String s5 = s1 + s2;
等价于new StringBuilder().append("a").append("b").toString();
StringBuilder 的toString()
方法新建了一个String对象,储存在堆中.
所以s4!=s5
.
StringTab JDK1.8 intern() 存的是对象还是引用?
public static void main(String[] args) throws InterruptedException {
test();
System.gc();
System.out.println(System.identityHashCode("ab"));
}
public static String test(){
String testA = new String("a") + new String("b");
System.out.println(System.identityHashCode(testA));
String testB = testA.intern();
System.out.println(System.identityHashCode(testB));
return testB;
}
输出
1321522194
1321522194
935563448
可知,当StringTab 不存在某个字符串时,intern 存入的是当前对象的引用,当垃圾回收时,该引用指向堆中对象会被正常回收.