如何理解java中的字符串常量池

一、字符串的创建过程

public class TestString {
    public static void main(String[] args) {
        String str = "abc";
        String str1 = "abc";
        String str2 = new String("world");
        System.out.println(str==str1); // true
        }
}
  • 当java文件编译成class文件时,会将Java文件中的字符串字面值当作常量放在class文件中的常量池里面(注意:这个常量池和运行时常量池不是一回事)。

  • 当JVM加载TestString.class文件时,class文件的信息会被解析到内存当中的方法区,class文件里常量池的大部分数据被放到了运行时常量池。 所以加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在StringTable中并没有相应的引用,在堆中也没有对应的对象产生),那么什么时候才会在堆中创建对象,并且将引用放入到字符串常量区呢?

  • 程序开始运行之后有个Idc指令,这个指令简单的说就是找到该运行时常量池中的常量将该常量从常量池推送至栈顶,简而言之,这个指令完成了String对象的创建,并且在全局的字符串常量池留下了它的引用。需要注意的是,这个指令来触发resolve,所以在resolve的过程中,如果发现StringTable已经有了内容匹配的String对象的引用,则将这个引用直接返回,如果StringTable中没有内容匹配的String实例的引用,则会在java堆里创建了一个对应内容的String对象,然后在StringTable记录下这个引用,并将其返回出去。可见Idc指令是否需要创建新的String实例,取决于执行这条指令时,StringTable中是否已经有了这个内容的引用

  • 对上图的这段代码进行分析:
    在编译阶段,会将上述出现的字符串字面值包括"abc" “hello” "world"都会放入class文件的常量池,然后开始加载,类加载开始不会创建实例,更不会在字符串常量池保存引用。只是把这些常量放入TestString类的运行时常量池。运行main方法时,通过idc指令将"abc"送到栈顶,即在堆中创建"abc"这个字符串对象,并且会将他的引用保存在字符串常量池中。然后返回这个引用给变量str,之后执行第二行代码,同理通过idc指令发现常量池中已经有该内容的对象,所以直接返回这个引用给str1,这个时候,堆中只有一个对象,而有三个引用指向它,str 和 str1 和常量池中保存的引用。当程序继续执行到new String(“world”)的时候,首先"world"这个字符串对象会被创建并且在常量池中保存了它的引用,但是这个是执行执行"world"时产生的对象,然后再执行new String(“world”),查看new String(String original)的源码如下:

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
  • 可以看到它是将original的值进行了拷贝,创建了另一个对象,所以这里是将"world"这个字符串对象的内容进行了赋值,在堆上创建了另一个String对象。值得注意的是,这个新建的对象并没有被放到字符串常量池中。

  • 我自己画了一份内存图,如下:上述程序的内存图

二、对intern()的解释

String调用intern()方法会返回在常量池中具有相同的该字符串内容的对象引用。如果没有则将当前对象在堆中的地址入池。

		String s = new String("hello");
        /* 这里创建了两个对象,一个是字面值创建了一个对象,
           引用在字符串常量区。然后通过new String(String str)将这个引用传到了[添加链接描述](https://www.zhihu.com/question/55994121)
           构造函数里面创建了另一个对象,这两个对象在堆中是不一样的地址,同时
           通过new的这个对象,并没有在常量池中。
        */
        String s2 = s.intern(); //这里将找到字符串常量区当中保存的那个对象的地址
        System.out.println(s==s2); // 这两个对象地址是不一样的,所以false
        //-------------------------------------------------------------------------------------
        String str = new String("hello") + new String("world"); 
        // helloworld 尚未入池,但hello,world都入池了,并且在堆中应该由两个hello对象,两个world对象。
        str.intern();// helloworld 入池
        String str2 = "helloworld"; //因为池里已经有了helloworld,所以str2和str指向的同一个地址。
        System.out.println(str==str2);// 答案为true
        

三、参考资料

证明了stringtable保存的是地址
解释了字面量是何时进入字符串常量池
整体解释了字符串的内存安排,但是有一些地方写的有些易混淆,比如new对象这一块,但是最后对代码的分析可以借鉴
这个老哥写的也不错,但是也是对new创建对象这一块,写的不是很清楚,至少内存图应该画的有问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值