字符串常量池
在JAVA语言中有8种基本数据类型和String。为了使它们在运行过程中速度更快、更节省内存,提供了对应的常量池。常量池就类似于一个JAVA系统级别提供的缓存。8种数据类型的常量池都是系统协调的,String类型的常量池(StringTable)比较特殊。它的使用方式有两种:
- 直接使用双引号声明,此String对象会直接存储在常量池中,比如:String str = “Hello World”;
- 使用String提供的intern()方法将字符串放入常量池中。
常量池位置变化
- JDK6以前,字符串常量池存放在永久代
- JDK7中将字符串常量池的位置调整到了JAVA堆中
- 所有的字符串都保存在堆中,这样我们在调优时仅仅需要调整堆的大小即可
为什么调整?
- permSize默认大小比较小
- 永久代垃圾回收频率低
StringTable特性
- 字符串常量池中不会存储相同内容的字符串
- 字符串常量池是一个固定大小的Hashtable,默认长度为1009。如果放入常量池中的String非常多就会造成严重的哈希冲突,从而导致链表变很长(拉链法解决哈希冲突),这会直接导致调用intern方法时的性能大幅度下降
- 使用
-XX:StringTableSize
可设置StringTable的长度 - 在JDK6中StringTable长度是固定的,为
1009
,所以如果常量池中的字符串过多就会导致效率下降很快。我们可以随意设置其长度 - 在JDK7中,StringTable的长度默认为
60013
,从JDK8开始,1009是可设置的最小值
谜一样的拼接操作
在考察字符串相关知识点时字符串的拼接一直是重点和难点,我们不知道这是新建对象还是放入了常量池中。首先给出以下结论:
- 常量与常量的拼接结果在常量池中,原理是编译期优化
- 常量池中不会存在相同内容的常量
- 只要其中有一个是变量,结果就在堆中。拼接原理是StringBuilder
- 如果拼接的结果调用intern方法,则主动将常量池中还没有的字符串对象放入常量池中,并返回对象地址
验证一
查看对应字节码文件:
ldc: 将常量池中的数据压入操作数栈
借助IDEA的反编译插件我们可以发现字符串的拼接被优化了:
验证二
不难发现在第二次打印字符串时整个类中的String对象并未增加,说明常量池中只存在唯一的一个字符串。
验证三
查看对应字节码文件:
str2的拼接操作是通过生成一个StringBuilder进行append实现的。这里会涉及到一个经典面试题:【new String(“a”)+new String(“b”)共生成几个字符串?】
注意使用final修饰的变量:
查看对应字节码文件:
使用final修饰的变量的拼接依旧会被编译器优化哦~
深入理解intern()
如果常量池中存在当前字符串,就会直接返回当前字符串。如果常量池中没有此字符串,会将此字符串放入常量池中后,再返回对象引用。很遗憾,这是一个native方法,我们可以通过查看OpenJdk7的源码一探究竟。
创建了几个对象?
经典面试题:new String(“ab”)会创建几个对象?
答案是两个。但是,许多同学不知道为什么?该如何证明呢?看字节码即可啦~~
不难发现创建了两个对象,一个是new出来的对象,还有一个是常量池中的ab(ldc #3 )。
拓展:new String(“a”)+new String(“b”)呢?
单纯从字节码可以发现创建了5个对象:
- new StringBuilder();
- new String(“a”);
- 常量池中的”a”;
- new String(“b”);
- 常量池中的”b”;
注意StringBuilder的toString方法:
天真的你也许会说我知道了,还有两个对象:
- new String(“ab”);
- 常量池中的”ab”;
查看toString方法的字节码:
强调:toString的调用在常量池中没有生成”ab”。
JDK6 VS JDK7?
经典问题:请问下方代码在JDK6和JDK7中的运行结果是什么?
- JDK6中:false false
- JDK7中:false true
讲解开始
对应内存情况如下图所示:
拓展
仅仅将s3.intern()和s4变换了位置,答案已经变为false了。
讲解开始
对应内存情况如下图所示:
intern总结
- JDK1.6中,将这个字符串对象尝试放入字符串常量池
- 如果常量池中有则不会放入,返回已有的对象地址
- 如果没有会把此对象复制一份(深拷贝),放入常量池,并返回对象地址
- 自JDK1.7起,将这个字符串对象尝试放入字符串常量池
- 如果常量池中有则不会放入,返回已有的对象地址
- 如果没有,会把对象的引用地址复制一份,放入常量池,并返回对象地址
intern习题
上方两题在不同JDK版本中的运行结果是什么呢?
那么这两题在同一JDK版本中会有不同吗?
欢迎评论区回答哦!