常量池之字符串常量池String.intern()

运行时常量池是方法区(PermGen)的一部分。

需要提前了解:
1. JVM内存模型
2. JAVA对象在JVM中内存分配

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

  • Java的自动装箱中其实就使用到了运行时常量池。详见:Java 自动装箱与拆箱的实现原理
  • 还有字符串常量池。

字符串进入到常量池的两种方法:

1. new String()的实例调用intern()方法。
    执行intern()方法时,若常量池中不存在等值的字符串,JVM就会在常量池中 创建一个等值的字符串,然后返回该字符串的引用。
2. “”(引号)引起来的内容(字面量)。
    引号引起来的字符串,首先从常量池中查找是否存在此字符串,如果不存在则在常量池中添加此字符串对象,然后引用此字符串对象。如果存在,则直接引用此字符串。

重要提示:虚拟机启动时常量池中就存在“java”字符串实例,下面代码中s2调用intern()方法时,只是返回常量池中“java”实例的引用,而没有添加“java”实例。

先看下,下面的代码

public class Test {
    public static void main(String[] args) {
        String s1 = new StringBuilder().append("aa").append("bb").toString();
        System.out.println(s1.intern() == s1);
        String s2 = new StringBuilder().append("ja").append("va").toString();
        System.out.println(s2.intern() == s2); 
    }
}

输出结果如下:

JDK6

false
false

JDK7、JDK8

true
false

为什么不同版本的JDK输出的结果还不一样呢?带着疑问我们分别分析下JDK1.6 和JDK1.7-JDK1.8的常量池内存模型。

JDK1.6 常量池内存模型

常量池之字符串常量池String.intern()_第1张图片
Paste_Image.png

如图中,我们可以看到常量池位于方法区(PermGen),常量池中引用的字符串实例也在常量池中。

  1. 通过调用intern()方法,会在常量池中生成一个相同字符串的对象
  2. “”内的字符串都会添加到常量池中,相当于引用的方法区中的字符串对象。

根据上图内存模型然后我们在分析下代码:

String s1 = new StringBuilder().append("aa").append("bb").toString();
    System.out.println(s1.intern() == s1);
    s1生成的对象在堆中,而s1.intern()的对象在常量池中,所以返回false。

String s2 = new StringBuilder().append("ja").append("va").toString();
    System.out.println(s2.intern() == s2);
    s2生成的对象在堆中,而s2.intern()的对象也肯定在常量池中,所以也返回false。

JDK1.7-JDK1.8常量池内存模型

常量池之字符串常量池String.intern()_第2张图片
Paste_Image.png

如图中,我们可以看到常量池位于方法区(PermGen),但常量池引用的字符串实例在中。(区别在这)

  1. 通过调用intern()方法,会在常量池添加一个此字符串实例的引用,(前提:常量池中没有相同内容的字符串)。
  2. “”内的字符串实例引用会添加到常量池中(前提:常量池中没有相同内容的字符串),如果常量池中存在,则引用常量池中的对象(防止重复创建对象)。

根据上图内存模型然后我们在分析下代码:
    String s1 = new StringBuilder().append("aa").append("bb").toString();
    System.out.println(s1.intern() == s1);
    s1生成的对象在堆中,此时常量池中没有跟s1内容相同的字符串,所以在调用intern方法时,会在常量池中添加此对象的引用,所以返回为true。

String s2 = new StringBuilder().append("ja").append("va").toString();
    System.out.println(s2.intern() == s2);
    s2生成的对象在堆中,而此时常量池中已经有一个跟s2内容相同的字符串常量,当s2调用intern方法时,返回常量池中已经存在的实例(相当于堆中有两个相同内容的实例:一个是new 出来的,一个是常量池中的)所以返回的结果为false。

“java”字符串实例为什么会在虚拟机启动时就存在?

请看下面的例子

 public void meme(){
    String s = new StringBuilder().append("me").append("me").toString();
    System.out.println(s.intern()==s);   //false
}

执行结果为false,说明“meme”也已经存在常量池中了。
    常量池定义:用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
    当类加载到内存后会在常量池中添加类、方法等符号的引用。方法名meme就会以字符串的方式加入到字符串常量池中,所以会感觉常量池中会内置很多字符串常量。

想了解更多精彩内容请关注我的公众号

常量池之字符串常量池String.intern()_第3张图片

你可能感兴趣的:(常量池之字符串常量池String.intern())