字符串常量池
在JDK7之后,字符串常量池从运行时常量池中分离,放到堆中。
String::intern
如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象,否则在字符串常量池创建对象。
以下代码分析基于JDK7以上
public static void main(String[] args) {
String s = new String("1");//字符串常量池由于class文件的加载,故有"1",堆区创建一个对象为"1",但s指向的是堆区的"1"而非常量池的"1"
s.intern();//尝试将"1"放入堆区中的字符串常量池,但此时已有,故返回字符串常量池中的引用
String s2 = "1";//默认先找字符串常量池有无"1",此时有则直接指向常量池中的"1"
System.out.println(s == s2);//false
String s3 = new String("1") + new String("1");//堆区新增两个"1"对象(共3个不在字符串常量池中的"1"),和一个"11"对象,但字符串常量池 没有 没有 没有 "11"!!!
String s5 = s3.intern();//查找常量池中是否有"11",此时没有,直接保存堆中的"11"的s3引用!!!到字符串常量池;
System.out.println(s3 == s5);//true,s3、s5均指向堆区的"11";
String s4 = "11";//默认先找字符串常量池有无"11",此时常量池直接是堆区的"11"引用,故s4指向的是s3
System.out.println(s3 == s4);//true
}
图中的双箭头表示引用变量实际指向的内存。
public static void main(String[] args) {
String s = new String("1");//字符串常量池在加载class的时候已经有一个"1",堆创建一个"1",s指向堆中的"1"
String s2 = "1";//s2指向字符串常量池中的"1"
s.intern();//查找字符串常量池中是否有"1",此时有则不创建
System.out.println(s == s2);//false
String s3 = new String("1") + new String("1");//堆中新增两个匿名"1",和一个"11",s3指向堆中的"11"
String s4 = "11";//字符串常量池新增一个"11"由s4指向
s3.intern();//查找字符串常量池中是否有"11",此时有则不创建
System.out.println(s3 == s4);//false
}
综上:
- 过程:加载class文件(含静态常量池);将静态常量池放入方法区中的运行时常量池;读取到某个字面量str,如果字符串常量池中没有对应的引用,则在堆中创建字面量str,字符串常量池保存str的引用,否则不动。
字符串常量池可以认为是通过堆来保存池中的字符串常量,它自身维护一个哈希表(保留引用) - 能够向字符串常量池添加字符串常量只有两种方式:
- 懒加载字面量:运行时执行到具体的代码处扫描到字面量,才会向字符串常量池中添加对象。
String::intern()
,不再在堆中创建对象,直接保存对应的引用到字符串常量池中。
- 如果字符串常量池内已有,则直接返回引用,否则在堆中
new
一个,再返回引用。