池这个词在java里并不罕见,比如运行时常量池,字符串常量池,线程池,数据库连接池等,所谓“池”就是为了资源复用,减少空间的占用,提高性能。
1、什么是字符串常量池?
JVM为了提高性能,减少内存开销,维护的一个存放字符串常量的内存区域,里面的字符串不允许重复,有长度限制,最大为65535字节(有兴趣的可以参考我的这篇博客:String的长度限制)。
2、字符串常量池在哪?
在JDK1.7以前,字符串常量池在方法区中,JDK1.7及以后的版本字符串常量池在堆中。
如下图所示(简单示意,就不展开了):
3、什么样的字符串会放入池中?
我们平时使用String比较多的方式就是字面量赋值,String s = "abc"
,还有StringBuilder/StringBuffer
,还有在类里定义的字符串变量,类里定义的字符串变量属于对象的一部分,不会存放在字符串常量池中。下面就分别讲一下这几种方式
为了对比,把这种不常用的方式String s = new String("abc")
也讲一下吧。
3.1、字面量赋值String s = "xxx"
方式
String s1 = "长得帅弹玻璃球都帅";
String s2 = "长得帅弹玻璃球都帅";
System.out.println(s1 == s2);
结果:
s1和s2其实是指向了同一块内存地址,并且该内存地址在字符串常量池中,可见字面量赋值会直接放入字符串常量池中。
内存分布:
3.2、String s = new String("xxx")
方式
这种方式不常用,但为了和String s = "xxx"
对比还是讲下
String s1 = "长得帅弹玻璃球都帅";
String s2 = "长得帅弹玻璃球都帅";
String s3 = new String("长得帅弹玻璃球都帅");
String s4 = new String("长得好看套麻布袋都好看");
String s5 = new String("长得好看套麻布袋都好看");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s4 == s5);
结果:
可见s3并没有在字符串常量池中,new出来的是对象,分配在堆上(注意并不是所有的对象都分配在堆上,这里我们不做讨论)
s4与s5是堆上的两个不同对象,自然不等,但他们堆上的对象都指向常量池里的"长得好看套麻布袋都好看"
对于new String("xxx")
这种,如果常量池中没有xxx字符串,则会在常量池中建一个xxx字符串,比如这里的s3,s4
如果常量池中有了xxx字符串,则其堆上的对象会指向常量池里的xxx字符串,比如这里的s5
内存分布:
我们用javap命令看下常量池:
如果面试官问你,String s = new String("xxx")
创建了几个对象,答曰:两个,字符串常量池里一个,堆上一个(如果结合xxx是否已经在常量池里存在分析,把内存分布图画出来,offer+1,哈哈)
3.3、StringBuilder/StringBuffer
方式
用StringBuilder/StringBuffer
最后会调用toString()
方法,而他们的toString()
方法都是调的new String
方法,所以和2.2一样
StringBuffer的toString()方法:
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
StringBuilder的toString()方法:
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
4、intern方法
当一个String对象调用intern()方法时,如果常量池中已经有同样的字符串了,则返回该对象的引用(在堆中就返回堆中的引用,在池中就返回池中的引用),如果没有,则将该对象添加到池中,并返回池中的引用。
我在JDK1.8-java.lang.String类源码阅读有写过intern方法
5、小实验
String s1 = "hello"+"world";
String s2 = new String("abc");
String s3 = s2 +"def";
内存分布:
我们用javap命令看下常量池: