常量池中的信息都会被加载到运行时常量池中。
String table又称为String pool,字符串常量池,其存在于堆中(jdk1.7之后改的)。最重要的一点,String table中存储的并不是String类型的对象,存储的而是指向String对象的索引,真实对象还是存储在堆中。
此外String table还存在一个hash表的特性,里面不存在相同的两个字符串。
此外String对象调用intern()方法时,会先在String table中查找是否存在于该对象相同的字符串,若存在直接返回String table中字符串的引用,若不存在则在String table中创建一个与该对象相同的字符串。
package com.zeng;
public class StringTableTest {
public static void main(String[] args) {
String s1="a";
/**
* 0: ldc #2 // String a 把a符号变为"a"字符串对象
* 2: astore_1 //将其放入本地变量表中slot为1的位置: 3 55 1 s1 Ljava/lang/String;
*/
String s2="b";
/**
* 3: ldc #3 // String b 把b符号变为"b"字符串对象
* 5: astore_2 //将其放入本地变量表中slot为2的位置: 6 52 2 s2 Ljava/lang/String;
*/
String s3="a"+"b";
/**
* 6: ldc #4 // String ab 把ab符号变为"ab"字符串对象
* 8: astore_3 //将其放入本地变量表中slot为3的位置: 9 49 3 s3 Ljava/lang/String;
*/
String s4=s1+s2;
/**
*9: new #5 // class java/lang/StringBuilder 新建一个StringBuilder对象
*12: dup
*13: invokespecial #6 // Method java/lang/StringBuilder."":()V 执行StringBuilder的构造函数
*16: aload_1 //获取本地变量表中slot为1位置压入的字符串池中的值:a
*17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
* 调用StringBuilder.append("a")
*20: aload_2 //获取本地变量表中slot为1位置压入的字符串池中的值:b
*21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
* 调用StringBuilder.append("b")
*24: invokevirtual #8 // Method jav/lang/StringBuilder.toString:()Ljava/lang/String;
* 调用StringBuilder.toString(),源码:return new String(value, 0, count);
* 也就是在堆中重新申请一块内存,返回其引用地址
*27: astore 4 //将其放入本地变量表中slot为4的位置:29 29 4 s4 Ljava/lang/String;
*/
String s5="ab";
String s6="a"+"b";
/**
* 29: ldc #4 // String ab 把ab符号变为"ab"字符串对象
* 31: astore 5 //将其放入本地变量表中slot为5的位置:33 25 5 s5 Ljava/lang/String;
*/
String s7=s4.intern();
/**
*33: aload 4 //获取本地变量表中slot为4位置压入的字符串池中的值:ab
*35: invokevirtual #9 // Method java/lang/String.intern:()Ljava/lang/String;
* 调用String.intern()方法:
* 编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。
*38: astore 6 //将其放入本地变量表中slot为5的位置:40 18 6 s6 Ljava/lang/String;
*/
System.out.println(s3==s4);
/**
* 分析:false
* s3会将字符串"ab"放入到字符串常量池中,而s4是在堆中,所以不相等
*/
System.out.println(s5==s6);
/**
* 分析:true
* javac在编译时 "a"+"b" 会编译成 "ab"
*/
}
}
javap -v StringTableTest.class
Classfile /Users/zengyunhua/Documents/myWork/spring-boot-starter-demo/testProject3/target/classes/com/zeng/StringTableTest.class
Last modified 2020-3-20; size 1110 bytes
MD5 checksum 319f6b9473d86cec4458ea5f770d29cb
Compiled from "StringTableTest.java"
public class com.zeng.StringTableTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#39 // java/lang/Object."":()V
#2 = String #40 // a
#3 = String #41 // b
#4 = String #42 // ab
#5 = Class #43 // java/lang/StringBuilder
#6 = Methodref #5.#39 // java/lang/StringBuilder."":()V
#7 = Methodref #5.#44 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#45 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #46.#47 // java/lang/String.intern:()Ljava/lang/String;
#10 = Fieldref #48.#49 // java/lang/System.out:Ljava/io/PrintStream;
#11 = Methodref #50.#51 // java/io/PrintStream.println:(Z)V
#12 = Class #52 // com/zeng/StringTableTest
#13 = Class #53 // java/lang/Object
#14 = Utf8
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/zeng/StringTableTest;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 s1
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 s2
#28 = Utf8 s3
#29 = Utf8 s4
#30 = Utf8 s5
#31 = Utf8 s6
#32 = Utf8 s7
#33 = Utf8 StackMapTable
#34 = Class #24 // "[Ljava/lang/String;"
#35 = Class #54 // java/lang/String
#36 = Class #55 // java/io/PrintStream
#37 = Utf8 SourceFile
#38 = Utf8 StringTableTest.java
#39 = NameAndType #14:#15 // "":()V
#40 = Utf8 a
#41 = Utf8 b
#42 = Utf8 ab
#43 = Utf8 java/lang/StringBuilder
#44 = NameAndType #56:#57 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#45 = NameAndType #58:#59 // toString:()Ljava/lang/String;
#46 = Class #54 // java/lang/String
#47 = NameAndType #60:#59 // intern:()Ljava/lang/String;
#48 = Class #61 // java/lang/System
#49 = NameAndType #62:#63 // out:Ljava/io/PrintStream;
#50 = Class #55 // java/io/PrintStream
#51 = NameAndType #64:#65 // println:(Z)V
#52 = Utf8 com/zeng/StringTableTest
#53 = Utf8 java/lang/Object
#54 = Utf8 java/lang/String
#55 = Utf8 java/io/PrintStream
#56 = Utf8 append
#57 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#58 = Utf8 toString
#59 = Utf8 ()Ljava/lang/String;
#60 = Utf8 intern
#61 = Utf8 java/lang/System
#62 = Utf8 out
#63 = Utf8 Ljava/io/PrintStream;
#64 = Utf8 println
#65 = Utf8 (Z)V
{
public com.zeng.StringTableTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zeng/StringTableTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=8, 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."":()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 // String ab
31: astore 5
33: ldc #4 // String ab
35: astore 6
37: aload 4
39: invokevirtual #9 // Method java/lang/String.intern:()Ljava/lang/String;
42: astore 7
44: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
47: aload_3
48: aload 4
50: if_acmpne 57
53: iconst_1
54: goto 58
57: iconst_0
58: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
61: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
64: aload 5
66: aload 6
68: if_acmpne 75
71: iconst_1
72: goto 76
75: iconst_0
76: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
79: return
LineNumberTable:
line 6: 0
line 12: 3
line 18: 6
line 24: 9
line 40: 29
line 41: 33
line 47: 37
line 56: 44
line 62: 61
line 67: 79
LocalVariableTable:
Start Length Slot Name Signature
0 80 0 args [Ljava/lang/String;
3 77 1 s1 Ljava/lang/String;
6 74 2 s2 Ljava/lang/String;
9 71 3 s3 Ljava/lang/String;
29 51 4 s4 Ljava/lang/String;
33 47 5 s5 Ljava/lang/String;
37 43 6 s6 Ljava/lang/String;
44 36 7 s7 Ljava/lang/String;
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 57
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 80 /* same_locals_1_stack_item */
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringTableTest.java"
注意事项
- 常量池中的字符串公是符号,第一次使用时才会变成对象。
- new String都是在堆上创建字符串对象。当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。
- 通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
- 常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用。
- 对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)。
final String str1=”ja”;
final String str2=”va”;
String str3=str1+str2;
在编译时,直接替换成了String str3=”ja”+”va”,根据第三条规则,再次替换成String str3=”JAVA”- 常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象
- 可以使用String.intern()方法,主动将串池中还没有的字符串(动态创建的)对象放入串池。JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,1.6会复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。
public class StringTableTest2 { public static void main(String[] args) { String s=new String("a")+new String("b"); String s1=s.intern(); System.out.println(s1=="ab"); System.out.println(s=="ab"); } }
1.6运行结果为:true和false
1.7运行结果为:true和true
参考链接:https://blog.csdn.net/tyyking/article/details/82496901
调优:
- 调整-XX:StringTableSize=桶个数
- 如果程序中有大量的字符串,而且重复的比较多,可以考虑使用intern()方法,把字符串入池,这样可以去掉重复的,减少内存占用