美文网首页java 虚拟机01 java基础程序员
常量池之字符串常量池String.intern()

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

作者: jijs | 来源:发表于2017-04-24 23:45 被阅读954次

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

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

    常量池的好处

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

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

    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 常量池内存模型

    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常量池内存模型

    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就会以字符串的方式加入到字符串常量池中,所以会感觉常量池中会内置很多字符串常量。

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

    相关文章

      网友评论

      • ismallboy:请问下楼主,在jdk1.7的时候,String s = "jijs";这个语句,内存分配时怎么样的?是在head里面放“jijs”还是在常量池里放“jijs”的值的?或者说是head放“jijs”的值,常量池放head里“jijs”值的引用而已?这个问题上面的图描述的貌似有点困惑~
      • 0a7f507ecf61:“JDK1.7-JDK1.8常量池内存模型” 这部分貌似不太正确,jdk1.7常量池貌似已经没有放在permGen了,而jdk1.8则完全取消permGen了,用native memory实现这个功能,好像叫Metaspace

        Area: HotSpot
        Synopsis: In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.
        RFE: 6962931

        [1] http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#changes
        [2] http://ifeve.com/java-permgen-removed/
        jijs:@歪纳特 1.7常量池还在permgen中,只不过常量池中,只存String对象的引用
        0a7f507ecf61:@jijs 我只是说1.8没有permgen了,1.7的常量池貌似也不在permgen了
        jijs:JDK1.8的PermGen被Metaspace代替了,但还有是常量池的。
      • 2dd23abaab77:这帖子很适合我👍
        jijs:高兴就好:ghost:
      • 虚拟机:我也觉得楼主举的例子没讲清楚,s1 s2写法上没区别,为什么java会有,aabb提前没有,讲的不清楚这里。
        虚拟机: @jijs 好的,辛苦了博主
        jijs: @虚拟机 确认上面没说清楚,请看下面评论回复说明了,抽时间更新上
      • df5fc2486907:为什么虚拟机启动时常量池中就存在“java”字符串实例?
        Linc_bd31:回答的不够接地气
        jijs:jdk 中的rt.jar 有java的包名,应该有好多用到java字符串的地方。
        jijs:public void meme(){
        String s = new StringBuilder().append("me").append("me").toString();
        System.out.println(s.intern()==s);
        }
        例如:你执行这个,meme也会在字符串常量池中存在的,类加载的时候,已经把方法名加载到常量池中了,还有好多其它地方的字符串也会加载到字符串常量池中。

      本文标题:常量池之字符串常量池String.intern()

      本文链接:https://www.haomeiwen.com/subject/wmhmzttx.html