JVM

一个关于java常量池的研究

Posted by Kapa on 2018-07-22

笔者最近在学习jvm的时候偶然看到一篇关于jvm中字符串常量池的文章《String的intern方法详解》,感觉十分受益,但是在实践过程中发现文中部分观点表述不够准确,感谢作者分享的同时,也希望能够通过本文指正。

引言:

    在博文《String的intern方法详解》中,作者详细阐述了String的intern方法工作原理,并提出了以下观点。

使用new关键字创建String对象是会在常量池中创建一个字符串常量这个对象的(在常量池中没有这个对象的时候),当池子中已经有了这个字符串直接返回引用

    但是在实践过程中,发现new关键字创建字符串并非导致创建字符串常量的原因,而作者看到的new关键字创建了字符串常量对象的现象,可能是由于new String("xxx")中的字符串字面量参数"xxx"导致的。通过以下实践可以证明。

一、环境准备

  1. java环境要求在jdk7及以上
  2. IDE不做要求,本人使用IDEA

二、实践过程

1. 基础理论

    通过博文内容了解到,jdk7及以后hotspot jvm将字符串常量池从永久代转移到了堆内存区域,因此以下两条语句执行过后,返回结果为true。

1
2
3
4
5
6
7
public static void main(String[] args) {
// 创建了两个字符串匿名对象并将两个对象连接产生的字符串对象的引用赋值给s
String s = new String("hello") + new String("world");
// 判断s和其指向的对象在字符串常量池中对应常量对象的引用是否相等
System.out.println(s == s.intern());
// 结果:true
}

    博文中解释上述代码返回true的具体原因是:在调用intern方法前,字符串常量池中没有”helloworld”常量对象。调用intern方法时,会在字符串常量池中添加一个和s相等的引用,共同指向堆中s指向的”helloworld”字符串对象以节省空间,因此结果为true。
    对于该观点,笔者的疑问是:字符串常量池中的引用是在创建字符串对象时添加的,还是在调用intern方法时添加的。接下来将带着这些疑问进行实践。

2. 实践内容

    先不考虑上面理论阶段中字符串常量池中引用是在何时添加的,总之,通过上面的语句,若字符串常量池中无相同内容,可以在字符串常量池中添加一个和s相同的引用。
    因此,可以通过以下代码验证是否在调用new关键字创建字符串对象时也会同时在常量池创建一份对象:

1
2
3
4
5
6
public static void main(String[] args) {
String s1 = new String("helloworld");
String s = new String("hello") + new String("world");
System.out.println(s == s.intern());
// 结果:false
}

    这段代码在之前代码基础上添加了一行String s1 = new String("helloworld");,最终运行过结果变成了false。很显然,s指向的对象在添加引用到字符串常量池之前,字符串常量池已经有了相同内容,所以不会再添加和s一样的引用到常量池。这也就证明了通过new String("xxx")的方法创建字符串对象确实能够在常量池也创建一份相同的对象。但这是new关键字导致的吗?
    经过分析,我认为有可能是在new String("xxx")时,传入的”xxx”参数导致常量池创建了”xxx”字符串对象,因此编写如下代码进行验证。

1
2
3
4
5
6
7
public static void main(String[] args) {
char[] chararr = {'h','e','l','l','o','w','o','r','l','d'};
String s1 = new String(chararr);
String s = new String("hello") + new String("world");
System.out.println(s == s.intern());
// 结果:true
}

    结果又一次发生了翻转,变成了true,说明s1对象创建的语句没有导致常量池创建对象,而前后只是参数发生改变,因此证明"xxx"才是导致常量池创建对象的真正原因!
    而上面的疑问,s指向的对象对应的字符串常量对象是在何时创建的,可以这样解答:new关键字创建字符串的方式本身不会导致常量池对象的创建,而导致常量池对象创建的是字符串字面量参数"xxx"。因此我们可以知道,常量池中的对象的引用是在调用intern方法时添加的。
    而我们如果执行以下代码,结果会返回false,说明字符串字面量创建的常量对象和s指向的对象并非同一对象,而是一个新的对象。

1
2
3
4
5
public static void main(String[] args) {
String s = new String("helloworld");
System.out.println(s == s.intern());
// 结果:true
}

三、总结一下

  • new String("xxx")语句中new关键字(或者说String构造方法)不是导致字符串常量池对象创建的原因,而字符串字面量参数"xxx"才是真正原因
  • 字符串字面量和intern方法导致创建的字符串常量池的对象不同,前者创建的是新对象,而后者向字符串常量池添加了指向原对象的引用。

如有疑问,欢迎讨论:)