JavaSE源码分析(三):字符串常量池与String.intern()方法

目录

前言

String对象的创建

String.intern()方法

案例及说明:

结语


前言

相信大多数Java程序员都做过String s1 = new String("hello");创建了几个对象这种问题,并能脱口而出答案:创建了2个对象,先在字符串常量池中创建了个”hello“字符串对象,然后在堆中创建了一个字符串对象。

那么问题来了:

1、为什么要创建两个对象?这行代码究竟发生了什么?

2、字符串常量池中的内容可以在运行时添加吗?

3、随着JDK版本的变化,上述内容有什么改变吗?

如果你对上面的问题还有疑问,那么读完本文对你一定能有所帮助。

String对象的创建

我们都知道Java代码要被执行,需要先被编译成为class文件。class字节码文件(以最经典的Helloworld.class为例)的大致结构如下:

class二进制流

其中我们看到有一项:Constant pool,即常量池,它包括了方法符号引用、字段符号引用、类符号引用以及字符串常量这四个常量池。其中的字符串常量池就是用来记录class文件中的字面值字符串常量。由于本文说的就是字符串常量池,所以为了方便起见,下面所说的常量池默认指的就是字符串常量池。

class文件需要JVM解释成机器码才能被运行。当JVM加载class文件时,需要创建许多内存数据结构来存放class文件中包括常量池在内的字节数据。同时,JVM会自动为常量池中的字符串常量字面值创建新的String对象(intern字符串对象,又叫拘留字符串对象),然后把常量池的入口地址转变成拘留字符串的直接地址(常量池解析)。 

因此,我们说String s1 = new String("hello");创建了2个对象,先在字符串常量池中创建了个”hello“字符串对象,然后在堆中创建了一个字符串对象。

String.intern()方法

既然字面值字符串常量会被直接放入常量池中,那么其他字符串也可以放进常量池吗?

这里我们就要介绍一下今天的主角:String.intern()方法。String对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。

Java运行过程中,所有相同字面值的字符串常量只可能建立唯一一个拘留字符串对象。

需要注意的是,从JDK7开始,字符串常量池出现了如下变化:

1、在JDK6及以前的版本,字符串常量池是在方法区(Method Area)。而从JDK7开始,字符串常量池被移动到了堆(Heap)中。

2、从JDK7开始,字符串常量池中可以不用再存储一份对象,如果堆中已经有了这个字符串对象,可以直接存储堆中这个对象的引用,不用再创建一个对象了。楼主的理解是,堆中已有的这个字符串对象就成了拘留字符串对象。

因此,由于不同JDK版本的常量池的不同,造成了intern()方法的表现存在一定的差异。

案例及说明:

我们分别在JDK6和JDK8运行下面的代码。

public static void main(String[] args) {
    String s1 = new String("hello");
    s1.intern();
    String s2 = "hello";
    System.out.println(s1 == s2);

    String s3 = new String("hello") + new String("world");
    s3.intern();
    String s4 = "helloworld";
    System.out.println(s3 == s4);
}

答案是:JDK6: false  false ,JDK7: false  true。

我们来解释一下在JDK6和JDK7环境各行发生了什么。我们将存在的差异和关键点用不同颜色注明,来进行比较。

JDK6:

第一行:

(1)由于JDK6的常量池在方法区中,所以JVM为字面值常量"hello"在方法区中创建了一个拘留字符串对象,并且将常量池中这个字面值指向了这个拘留字符串对象地址

(2)在堆内存中创建了一个String对象,该对象的内容指向了常量池的"hello"

(3)栈内存中引用s1指向了堆中String对象的地址。

第二行:返回拘留字符串"hello"的地址。由于常量池中已经有"hello"拘留字符串,所以直接返回(但是由于没有接收返回值,所以可以认为这行代码没有任何作用)

第三行:由于已经有了拘留字符串"hello",且相同字面值常量的拘留字符串只能有一个,所以栈内存中s2引用直接指向了拘留字符串"hello"的地址

第四行:引用类型的==比较的是引用的地址,由于方法区和堆不在一块区域,所以常量池中拘留字符串对象的地址显然与堆内存中String对象的地址不同,所以打印结果为:false

第五行:这一行相当于String s3 = new StringBuilder().append(new String("hello")).append(new String("world")).toString();

(1)这行代码中在堆里创建的new StringBuilder()、new String("hello")和new String("world")和我们要讨论的无关,这里不多说。

(2)StringBuilder拼接字符串完成后通过toString()方法在堆里创建了一个new String("helloworld")字符串对象(这里并不会放入字符串常量池),栈内存中s3引用指向了堆内存中字符串对象的地址

第六行:返回拘留字符串"helloworld"的地址。由于常量池中没有"helloworld"拘留字符串,所以在常量池创建了一个"helloworld"拘留字符串,返回这个拘留字符串地址(但没有接收返回值)

第七行:已经有了拘留字符串"helloworld",所以栈内存中s4引用直接指向了拘留字符串"helloworld"的地址

第八行:同样由于方法区和堆不在一块区域,所以常量池中拘留字符串对象的地址显然与堆内存中String对象的地址不同,所以打印结果为:false

           所以JDK6的结果为false  false。

JDK8:

第一行:

(1)由于JDK6的常量池在堆中,所以JVM为字面值常量"hello"在中创建了一个拘留字符串对象,并且将常量池中这个字面值指向了这个拘留字符串对象地址

(2)在堆内存中创建了一个String对象,该对象的内容指向了常量池的"hello"

(3)栈内存中引用s1指向了堆中String对象的地址。

第二行:返回拘留字符串"hello"的地址。由于常量池中已经有"hello"拘留字符串,所以直接返回(但是由于没有接收返回值,所以可以认为这行代码没有任何作用)

第三行:由于已经有了拘留字符串"hello",且相同字面值常量的拘留字符串只能有一个,所以栈内存中s2引用直接指向了拘留字符串"hello"的地址

第四行:引用类型的==比较的是引用的地址,由于第一行是在堆中创建了两个对象,地址不同,所以打印结果为:false

第五行:这一行相当于String s3 = new StringBuilder().append(new String("hello")).append(new String("world")).toString();

(1)这行代码中在堆里创建的new StringBuilder()、new String("hello")和new String("world")和我们要讨论的无关,这里不多说。

(2)StringBuilder拼接字符串完成后通过toString()方法在堆里创建了一个new String("helloworld")字符串对象(这里并不会放入字符串常量池),栈内存中s3引用指向了堆内存中字符串对象的地址

第六行:返回拘留字符串"helloworld"的地址。常量池中没有"helloworld"拘留字符串,但是堆中有"helloworld"对象,所以常量池中直接存储了这个对象的引用,没有另外创建对象(但没有接收返回值)

第七行:已经有了拘留字符串"helloworld",所以栈内存中s4引用直接指向了拘留字符串"helloworld"的地址

第八行:由于拘留字符串对象实际就是堆中的"helloworld"对象,所以s3与s4的指向一致,所以打印结果为:true

结语

本文主要对不同JDK版本常量池的变化,以及造成String.intern()方法的表现差异进行了说明,希望能对广大读友有所帮助~不足之处欢迎指出交流~~

最后需要感谢这几位博主大大的博文,给了我很大帮助,非常感谢~

🔹深入解析String#intern

🔹Class文件内容及常量池

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值