字符串常量池中是不会存储相同内容字符串的,因为Spring Pool是一个Hashtable
String的String Pool是一个固定大小的Hashtable。如果放进Spring Pool的String非常多,就会造成Hashtable冲突严重,导致链表很长,链表长就会影响调用String.intern时性能会大幅下降。
使用-XX:StringTableSize可以设置HashTable长度。jdk8中StringTable默认是60013,1009是可设置的最小值
String内存分配
java6及之前,字符串常量池放在永久代
java7将字符串常量池的位置调整到了堆中,所有字符串都保存在堆中,和其他普通对象一样,这样可以在调优时仅需要调整堆大小。
java8虽然元空间改为了永久代,但是字符串常量池依旧保留在了堆中
为什么会出现以下结果
String s1 = "hello";
String s2 = "hello";
// 情况1
System.out.println("s1==s2:"+(s1 == s2));//true
String s3 = "he" + "llo";
String s4 = "hel" + new String("lo");
final String s5 = "word";
String s6 = "helloword";
// 情况2
System.out.println("s1==s3:"+(s1 == s3));//true
// 情况3
System.out.println("s1==s4:"+(s1 == s4));//false
// 情况4
System.out.println("s5==s6:"+(s5 == s6));//true
情况1没什么好说的,都是直接在字符串常量池里拿。
情况2和情况4实际是相同情况,都已经在编译器进行优化为了字符串常量,优化后结果和情况1相同
情况3实际在字节码指令中是先new了一个StringBuilder对象,调用其append方法拼接,再调用toString方法转化为String
那么StringBuilder的append方法效率高还是直接“+”效率高呢?
append效率高,因为每次“+”都相当于重新new StringBuilder,内存占用更大
那么修改为用append之后还能进一步优化吗?
在StringBuilder默认构造器中,其底层默认16大小char数组,随着字符串不断变大数组需要不断扩容。所以如果依旧确定数组大小不会超过某值的话直接使用有参构造指定数组大小
intern()的使用
jdk1.6:如果字符串常量池中有并不会放入,返回已有池中对象的地址
如果没有,把此对象复制一份,放入池中并返回池中对象地址(池中是和原对象内容相同的新对象)
jdk1.7:如果池中有不会放入,返回已有池中对象地址
如果没有,则把对象引用地址复制一份放入池中,并返回池中引用地址
new String(“ab”)会创建几个对象?
2个
new String(“a”)+new String(“b”)创建了几个对象
下图中框出5个
深入:实际StringBuilder的toString()的方法也new了一个String
所以是6个对象,并且常量池中没有“ab",下图为toString()的字节码指令
为什么字符串常量池中没有“ab”呢?我也不太懂,留给以后了
G1的String去重
- 访问堆上存活的对象。对每一个访问的对象都会检查是否是候选要去重的String对象。
- 如果是,把这个对象的一个引用插入到队列中等待后续处理。一个去重的线程在后台运行,处理这个队列。处理队列的一个元素意味着从队列删除这个袁术,然后尝试去冲引用的String对象
- 使用一个hashtable来记录所有的被String对象使用的不重复的Char数组,当去重的时候会检查这个hashtable,来看堆上是否已经存在一个一模一样的char数组。
- 如果存在,String对象会被调整引用哪个数组,释放原来的数组引用,最终会被垃圾收集器回收掉
- 如果查找失败,char数组会被插入到hashtable,这样以后的时候就可以共享这个数组了