三者关系
1、常量池:俗称静态常量池,存在于*.class文件中,就是一张表,虚拟机指令根据这张表找到要执行的类名、类方法、参数类型、字面量等信息
常量池:
虚拟机指令:
2、运行时常量池:类被加载时,其常量池信息会被放入运行时常量池,并把里面的符号地址变为真实地址。
3、StringTable 串池:运行时常量池的 一部分,储存字符串常量
StringTable
特性
1、stringTable数据结构为一个hash表(数组+链表),不可扩容,存字符串常量,唯一不重复。
2、常量池中的字符串仅是符号,第一次用到才变为对象
3、其创建方式为懒创建,用到时才创建。
如图:当类被加载时,a b ab 都是常量池中的符号,还未变成java字符串对象。
当图中1执行时,会先去stringtable中看看是否有“a”,发现没有,将“a”加入字符串常量池stringtable
2、3也是如此,当下次有类似String s4 = “ab” 时,stringtable会直接引用已存在的“ab” ,故s4==s3。
4、字符串变量的拼接原理是StringBuilder,如String s4 = s1+s2;//等价于new StringBuilder().append(“s1”).append(“s2”).toString,所以字符串变量拼接是new出了一个字符串对象,存在于java堆中
5、字符串常量拼接原理是编译期优化,String s3 = “a”+“b”;//直接寻找拼接之后的“ab”,若在stringTable中,直接引用,否则加入stringTable
6、可以使用String.intern()方法,主动将StringTable中还没存在的字符串对象加入StringTable
》6.1、jdk1.8中intern将字符串对象放入StringTable,如果有则不会放入,否则放入StringTable,最终都会返回StringTable中的对象
》6.2、jdk1.8中intern将字符串对象放入StringTable,如果有不放入,否则复制会把该对象复制一份放入StringTable,最终返回StrinTable中的对象
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a"+"b";
String s4 = s1+s2;//等价于new StringBuilder().append("s1").append("s2").toString
String s5 = "ab";
String s6 = s4.intern();
System.out.println("s3==s4:"+ (s3==s4));//false
System.out.println("s3==s5:"+ (s3==s5));//true
System.out.println("s3==s6:"+ (s3==s6));//true
String x2 = new String("c") + new String("d");
String x1 = "cd";//1
x2.intern();//2
//如果怕调换1、2顺序,则下面的结果为true,但前提是jdk1.6以上,若是jdk1.6则还是false
System.out.println("x2==x1:"+ (x2==x1));//false
final String a = "123";//
final String b = "456";
String c = "123456";
String d = a + b;//此处a、b被final修饰,相当于常量
System.out.println("c == d:"+ (c == d));//true
}
串池位置
1、jdk1.6前存在方法区的永久代中
2、jdk1.6后存在堆中
永久代中只有fullGC才可回收,回收效率低,浪费内存
StringTable垃圾回收
因为StringTable为不可扩容的Hash表,所以内存满时会进行垃圾回收
StringTable性能调优
1、如字符串常量较多(如读取大文件)可调整-XX:StringTableSize=桶个数
减少串池hash冲突
2、考虑通过intern将字符串对象入池,减少内存开销
未入池的字符串对象每个都要再堆中分配内存,而在串池中重复的字符串引用同一个字符串对象
参考:java中级程序员必会的教程,解密JVM【黑马程序员出品】