探究JVM(二)理解JDK 7 中的String.intern()

引言:String.intern() 被用来往字符串常量池中添加字符串,使得字符串的值可以复用,从而降低了内存开销。String.intern() 在JDK 7 中发生了重大变动,理解String.intern()改动后的原理能帮助我们正确使用它。

1 比较String两种创建方式

先来看下边一段代码

public class Main {
     //第一段代码
    /**
     * @author 姚子威
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        String s= new String("1")+new String("1");
        String s1= "11";
        System.out.println(s==s1);
    }
}

*运行的结果:false

第一次通过两个字符串对象"1"的拼接,形成一个新的"11"字符串对象,这个"11"字符串对象是储存在堆中。JVM直接返回这个对象的引用给s变量。这时候字符串常量池中还没有"11"的引用,此时引用s指向堆中
第二次通过字面量"11"创建,此时JVM会在字符串常量池中创建"11"字符串常量,并且返回新创建的"11"字符串的引用。引用s1指向字符串常量池
有一点需要解释一下,上下文语境提到的堆指的是除了字符串常量池之外的堆内存空间。通过上文,可以知道s和s1指向的地址完全不同,所以结果返回的是false。
读者可能不解,在上述例图中字符串常量池有"1"字符串,我们将在下面的第三小节解释为什么会出现这种情况。

2 理解String.intern()工作原理

我们对上述代码做一点修改,加入String.intern(),看看会发生什么事情。

public class Main {
   //第二段代码
    /**
     * @author 姚子威
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        String s= new String("1")+new String("1");
        s.intern();
        String s1= "11";
        System.out.println(s==s1);
    }
}

*运行的结果:true

第二段代码和第一段代码唯一不同的地方就是加入了s.intern() ,这时候我们惊奇得发现返回的结果是true,这又是怎么一回事呢?
intern()方法的作用是先检查字符串常量池中是否有调用者(在这个例子中是引用s)指向的字符串,如果有,返回字符串常量池中这个字符串的引用,如果没有,说明此时字符串对象在堆中,那就在字符串常量池中记录该引用。
在第一次创建字符串对象并返回引用s后,s调用了intern()方法,在字符串常量池中记录下引用s的值。当第二次通过字面量"11"创建时,直接从字符串常量池中返回"11"这个字符串对象的引用,即引用s的值。这样就能够理解为什么第二段代码返回的值是true。

3 事情还没结束

理解了第一段代码和第二段代码,我们来做一个小题目,我们把第二段代码再修改一下。下面是第三段代码

public class Main {
    //第三段代码
    /**
     * @author 姚子威
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        String s= new String("1");
        s.intern();
        String s1= "1";
        System.out.println(s==s1);
    }
}

请问得到的结果是什么?
我相信有一部分人会说 这跟第二段代码有什么区别?答案当然是true了。但是运行的结果是false!区别在哪里呢?

第二段代码第一次创建"11"是通过字符串的拼接得到的对象,本质上是通过StringBuilder.toString()得到并存放在堆中,所以此时字符串常量池中只有"1"字符串常量,没有"11"字符串常量。

而在第三段代码中,通过new String()方法 实际上做了两件事情,第一是创建了一个String实例在堆中,第二是创建了一个字符串常量在常量池中。这时候调用intern()方法,由于字符串常量池中已经有了"1"字符串,所以没有把堆中的实例的引用添加到字符串常量池中,此时s指向堆中的"1"实例,s1指向字符串常量池中的"1"字符串常量。

4 总结

(1) 通过字面量创建字符串,先检查常量池中是否有相同的字符串,如果没有,创建一个新的字符串实例并返回引用,如果有,直接返回该实例的引用。
(2) 通过构造器new String() 方法创建字符串,先在堆中创建实例并返回该引用,然后检查常量池中是否有相同的字符串常量,如果没有,在字符串常量池中创建一个相同的字符串常量,如果有,则仅在堆中创建Java层面的String实例。
(3)通过字符串拼接的而成的字符串实例不会出现在字符串常量池中,只会在堆中出现(如果是单纯字面量拼接则直接指向字符串常量池)。
(4)在JDK 7 以后String.intern()方法不会再复制字符串到常量池中,只会检查常量池中是否有相同的字符串,如果有,则返回该字符串的引用。如果没有,说明此时字符串对象在堆中,这时候只需记录下堆中对象的引用到常量池中,并且返回该引用。

5 结语

(1)对于字符串常量池及其相关知识点还是比较繁杂,博主也是基于已有认知进行总结,如果有错误的地方,希望大家能在评论区指出。
(2)本文的代码样例是参考美团技术团队的文章给出的。

6 参考

(1)《深入理解Java虚拟机》-周志明
(2) 美团技术团队文章:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值