JVM 学习笔记——字符串常量池(String Pool)

String Pool 简介

  • 字符串常量池(String Pool),也称 String Table,在 JDK1.7 后从方法区移到了堆中
  • 字符串常量池在 JDK1.7 后可以直接存储堆中的字符串的引用
  • 字符串常量池中的字符串不能重复

两种创建字符串的方式

字面量

String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);    //true

使用字面量创建一个字符串时,JVM 首先会在字符串常量池中查找是否存在 "abc",若不存在,则在字符串常量池中创建 "abc" 字符串对象,然后将池中 "abc" 字符串对象的引用返回,若存在,则直接将池中 "abc" 字符串对象的引用返回

这里显然 s2 为常量池中 ”abc“ 的引用,故输出 true

new 关键字

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);    //false

使用 new 关键字创建一个字符串时,JVM 首先会在字符串常量池中查找是否存在 "abc",若不存在,则在字符串常量池中创建 "abc" 字符串对象,再在堆中创建一个 "abc" 字符串对象,然后将堆中这个 "abc" 字符串对象的引用返回,若存在,则直接在堆中创建一个 ”abc“ 字符串对象,并返回引用

这里显然 s1 和 s2 指向堆中的不同字符串对象,故输出 false

面试题

如下代码创建了几个对象

String s = new String(“abc”);

 答案:一个或两个

当字符串常量池中不存在 ”abc“ 时,会首先在字符串常量池中创建 ”abc“,然后再将字符串常量池中的 ”abc“ 复制一份到堆中,由 s 指向,故创建两个

当字符串常量池中存在 ”abc“ 时,直接复制一份到堆中,并由 s 指向,故创建一个


字符串拼接原理

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
String s5 = "a" + "b";
System.out.println(s3 == s4);    //false
System.out.println(s3 == s5);    //true
System.out.println(s4 == s5);    //false

变量相加

对于字符串变量的相加(s1 + s2),本质上是先创建 new StringBuilder(),调用 append 方法进行拼接,最后调用 toString(),实际上还是 new String() ,故 s3 == s4 输出 false

常量相加

两个或者两个以上的字符串常量直接相加,在预编译的时候 “+” 会被优化,自动拼接成一个,故 String s5 = "a" + "b" 相当于 String s5 = "ab",显然 s3 == s5 输出 true


intern() 方法

intern() 方法是 String 类的一个 native 方法,当执行 str.intern() 时,JVM 会在字符串常量池中查找是否存在与 str 等值的 String,若存在,则直接返回字符串常量池中相应 String 的引用,若不存在,则会在字符串常量池中创建一个等值的 String,然后返回这个 String 在字符串常量池中的引用

因为 intern() 返回的是字符串常量池中的引用,故对于相同值的 String 对象,调用 intern() 方法的返回值,用 == 判断一定为 true

对于 intern() 方法,在 JDK1.7 之前和之后略有不同,如下示例

String s1 = new String("a");
s1.intern();
String s2 = "a" ;
System.out.println(s1 == s2);

String s3 = new String("a") + new String("b");
s3.intern();
String s4 = "ab" ;
System.out.println(s3 == s4);

//JDK1.7 之前:false false
//JDK1.7 之后:false true

JDK1.7 之前,字符串常量池存放在方法区(永久代)中,与堆完全分开,显然 s1 和 s3 均在堆中,而 s2 和 s4 均在方法区中,故输出 false false

JDK1.7 之后,字符串常量池不在方法区了,常量池中不需要再存储一份对象,而是可以直接存储堆中的引用,故执行 s3.intern() 时,将 s3 直接放入了字符串常量池,s4 同样指向这个引用,故 s3 == s4 输出 true,而 s1.intern() 执行时,发现字符串常量池中已经存在 "a" 了,故不进行存入,显然此时 s1 为堆中的对象,而 s2 为字符串常量池中的对象,故 s1 == s2 输出 false

也可以换一种说法

JDK1.7 之前,intern() 方法尝试将对象放入字符串常量池,若池中有则不会放入,若没有,则会把此对象复制一份放入,并返回池中对应的引用

JDK1.7 之后,intern() 方法尝试将对象放入字符串常量池,若池中有则不会放入,若没有,则直接放入,并返回池中对应的引用

完整示例

String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";
String s4 = s1 + s2;
String s5 = "ab";
String s6 = s4.intern();

System.out.println(s3 == s4);	//false
System.out.println(s3 == s5);	//true
System.out.println(s3 == s6);	//true

String x2 = new String("c") + new String("d");
String x1 = "cd";
x2.intern();

//false,若 String x1 = "cd"; 与 x2.intern(); 交换,为true
//交换后,如果是 JDK1.7 之前,为 false,因为复制了一份副本
System.out.println(x1 == x2);

调优

可通过 JVM 参数 -XX:StringTableSize 改变字符串常量池底层的 HashTable 的散列表长度,当系统的字符串常量个数过多,可以适当的调高该参数,以减少哈希冲突,提高性能

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值