方法区StringTable串池
StringTable是方法区的重要组成部分
字符串入池
案例:
public class Test2 {
public static void main(String[] args) {
String a = "a";
String b = "b";
String ab = "ab";
}
}
编译成class文件后又编译回来
Last modified 2021-2-21; size 508 bytes
MD5 checksum 6dfdd4fd20e3dfb96ca5557dca082e97
Compiled from "Test2.java"
public class com.cdyl.api.Test2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#25 // java/lang/Object."<init>":()V
#2 = String #18 // a
#3 = String #20 // b
#4 = String #21 // ab
#5 = Class #26 // com/cdyl/api/Test2
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/cdyl/api/Test2;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 a
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 b
#21 = Utf8 ab
#22 = Utf8 MethodParameters
#23 = Utf8 SourceFile
#24 = Utf8 Test2.java
#25 = NameAndType #7:#8 // "<init>":()V
#26 = Utf8 com/cdyl/api/Test2
#27 = Utf8 java/lang/Object
{
public com.cdyl.api.Test2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/cdyl/api/Test2;
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
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
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 10: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
3 7 1 a Ljava/lang/String;
6 4 2 b Ljava/lang/String;
9 1 3 ab Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "Test2.java"
常量池中的信息,都会被加载到运行时常量池中,这时a b ab 都还是常量池中的符号,还没有变为字符串对象
当运行到 ldc #2
会把 a 符号变为"a" 字符串对象 这时会去StringTable里面去找一次如果没有就会放入StringTable,如果有了则把StringTable里面的字符串对象返回 ,后面的 ldc #3
ldc #4
同理
字符串拼接原理
public class Test2 {
public static void main(String[] args) {
String a = "a";
String b = "b";
String ab = "ab";
String s = a + b;
}
}
同理进行反编译拿到jvm指令
Last modified 2021-2-21; size 691 bytes
MD5 checksum a373a0219f010580b0f60dcd3e8cbf1d
Compiled from "Test2.java"
public class com.cdyl.api.Test2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#30 // java/lang/Object."<init>":()V
#2 = String #22 // a
#3 = String #24 // b
#4 = String #25 // ab
#5 = Class #31 // java/lang/StringBuilder
#6 = Methodref #5.#30 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#32 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#33 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Class #34 // com/cdyl/api/Test2
#10 = Class #35 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/cdyl/api/Test2;
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 args
#21 = Utf8 [Ljava/lang/String;
#22 = Utf8 a
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 b
#25 = Utf8 ab
#26 = Utf8 s
#27 = Utf8 MethodParameters
#28 = Utf8 SourceFile
#29 = Utf8 Test2.java
#30 = NameAndType #11:#12 // "<init>":()V
#31 = Utf8 java/lang/StringBuilder
#32 = NameAndType #36:#37 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#33 = NameAndType #38:#39 // toString:()Ljava/lang/String;
#34 = Utf8 com/cdyl/api/Test2
#35 = Utf8 java/lang/Object
#36 = Utf8 append
#37 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = Utf8 toString
#39 = Utf8 ()Ljava/lang/String;
{
public com.cdyl.api.Test2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/cdyl/api/Test2;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
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/String;
27: astore 4
29: return
LineNumberTable:
line 6: 0
line 7: 3
line 8: 6
line 9: 9
line 10: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 args [Ljava/lang/String;
3 27 1 a Ljava/lang/String;
6 24 2 b Ljava/lang/String;
9 21 3 ab Ljava/lang/String;
29 1 4 s Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "Test2.java"
通过指令给我们的注释我们发现其实底层的是用了StringBuilder的append进行两次拼接,然后通过toString方法转为字符串后返回。
不看注释通过常量池自己定位也是可以的具体看上一篇文章,这里就不进行详细说明
最终执行的其实是String s= new StringBuilder().append(a).append(b).toString();
而toString是通过新创建一个字符串对象返回
所以说 ab和s 是不相同的 s是通过new String来的 而ab是在StringTable里的
代码验证也的确如此
编译期优化
还是先看一个例子
问ab是否和s是同一个对象?
不清楚还是反编译一下看看对象的常量池
Last modified 2021-2-21; size 481 bytes
MD5 checksum 2591a843da775e0d115c1c8ec58d3447
Compiled from "Test2.java"
public class com.cdyl.api.Test2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#22 // java/lang/Object."<init>":()V
#2 = String #16 // ab
#3 = Class #23 // com/cdyl/api/Test2
#4 = Class #24 // java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Utf8 LineNumberTable
#9 = Utf8 LocalVariableTable
#10 = Utf8 this
#11 = Utf8 Lcom/cdyl/api/Test2;
#12 = Utf8 main
#13 = Utf8 ([Ljava/lang/String;)V
#14 = Utf8 args
#15 = Utf8 [Ljava/lang/String;
#16 = Utf8 ab
#17 = Utf8 Ljava/lang/String;
#18 = Utf8 s
#19 = Utf8 MethodParameters
#20 = Utf8 SourceFile
#21 = Utf8 Test2.java
#22 = NameAndType #5:#6 // "<init>":()V
#23 = Utf8 com/cdyl/api/Test2
#24 = Utf8 java/lang/Object
{
public com.cdyl.api.Test2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/cdyl/api/Test2;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: ldc #2 // String ab
2: astore_1
3: ldc #2 // String ab
5: astore_2
6: return
LineNumberTable:
line 6: 0
line 7: 3
line 8: 6
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 args [Ljava/lang/String;
3 4 1 ab Ljava/lang/String;
6 1 2 s Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "Test2.java"
我们查看常量池发现只有一个String ab 那a,b又去哪了啊。
其实java在编译的时候如果发现是2个常量相拼接那么这个值就是确定的
比如
String s = "a" + "b";
其结果必然是ab那么在编译的时候就会直接编译成ab,我们打开编译过后的class文件用idea自带的反编译打开
看到的确定是String s = "ab";
而不是String s = "a" + "b";
,那么第一个ab变量被赋值的时候会同时放一份在StringTable中 而s赋值的时候发现StringTable已经有"ab"字符串了,那么就会直接用StringTable中的"ab"进行赋值那么 ab和s 就是同一个对象
intern() 主动入池
intern方法就是主动把字符串入池,在jdk1.6和jdk1.8返回的有稍许不同,1.6会先看看是否StringTable串池中是否有,如果没有则说明可以入池,那么他会自我复制一份把复制的那份入池,而在1.8中则不会复制而是直接把字符串对象入池
public class Test2 {
public static void main(String[] args) {
String a = "a";
String b = "b";
String s=a+b;
String ab = "ab";
System.out.println(s==ab);
}
}
前面以前说明了字符串拼接是通过StringBuilder的append进行两次拼接,然后通过toString方法转为字符串后返回,一个是堆里的字符串一个是StringTable串池里的字符串
所以肯定是false
public class Test2 {
public static void main(String[] args) {
String a = "a";
String b = "b";
String s=a+b;
s.intern(); // 入池操作
String ab = "ab"; // 那么这个ab也是从StringTable拿的
System.out.println(s==ab); // 那么s和ab就是一个对象
}
}
我们在ab之前就把s先入池,那么这时ab去常量池中那拿到ab符号转为字符串"ab",然后去StringTable中看发现已经有了那么就会拿StringTable中的返回那么s和ab就是同一个对象
验证一下的确是true。
现在我们把代码顺序改一下
public class Test2 {
public static void main(String[] args) {
String a = "a";
String b = "b";
String ab = "ab";
String s=a+b;
s.intern();
System.out.println(s==ab);
}
}
这时发现变成了false,这时咋回事啊
public class Test2 {
public static void main(String[] args) {
String a = "a";
String b = "b";
// ab初始化入池成功
String ab = "ab";
String s=a+b;
// s入池这时发现池中已经有ab了那么入池失败
s.intern();
// 所以s和ab就不是同一个对象
System.out.println(s==ab);
}
}
前面ab就已经入池成功了,后面s再入池就会失败因为里面已经有"ab"字符串了,所以s和ab就是2个不同的字符串
StringTable的位置
StringTable大小设置和调优
JVM参数设置: -XX:StringTableSize=60013
因为StringTable是类似于hashTable的结构这个设置的只是设置的哈希桶的大小(类似于数组最大的长度),如果字符串比较多设置小了会导致每个位置的链表过长导致查询插入过慢。