关于String.intern()和new StringBuilder("").append("").toString();

welldum 2019-06-25

在《深入理解Java虚拟机》书中,提到在jdk1.7的版本中用String.intern()返回引用。

public class RuntimeConstantPoolOOM {
    public static void main(String[]args) {
        String str1=new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern()==str1);
        String str2=new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern()==str2);
    }

}

运行结果:true false
书中给的解释是:

JDK 1.7(以及部分其他虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。

现在的疑问是“java”这个字符串在常量池中什么时候存在了?
我最开始的猜想是“java”这个字符串是不是常驻在常量池中的?那为什么常驻在常量池中呢?Java虚拟机什么时候加载了“java”这个字符串?

最开始以为是StringBuilder的原因,查看了一下StringBuilder的源码,发现里面没有加载字符串常量,网上也找了关于intern()的,发现都只是对比JDK 1.6和JDK 1.7之间上面代码的运行结果比较,后来找了许久,终于找到一篇关于[String.intern()探究]: <http://baijiahao.baidu.com/s?id=1568390319555291&wfr=spider&for=pc>的文章,发现要去查看System的源码.

java虚拟机会自动调用System类

/* register the natives via the static initializer.
 *
 * VM will invoke the initializeSystemClass method to complete
 * the initialization for this class separated from clinit.
 * Note that to use properties set by the VM, see the constraints
 * described in the initializeSystemClass method.
 */
在System类中的注释可以知道,调用了initializeSystemClass方法,在此方法中调用了Version对象的init静态方法
sun.misc.Version.init();
因此sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化。
查看Version类定义的私有静态字符串常量如下:
private static final String launcher_name = "java";
private static final String java_version = "1.7.0_51";
private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
private static final String java_runtime_version = "1.7.0_51-b13";
在初始化Version类时,对其静态常量字段根据指定的常量值做默认初始化,所以"java"被加载到了字符串常量池中,修改上面代码使字符串值为上面常量中的任意一个都会返回false。
String str2=new StringBuilder("1.7.0").append("_51").toString();
System.out.println(str2.intern()==str2);

这个问题解决了,然后我又发现了另外一个问题。除了这些在虚拟机加载时就初始化的常量,定义其他的字符串常量,比如“nihao”.

先运行这个代码
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
通过上面的解释,运行结果为true.
在运行这个代码
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
其结果是什么?应该还是true吧,毕竟通过上一个运行结果可以知道"nihao"这个字符串常量没有被预先加载到常量池中。
但是运行结果却是false.

我现在还没想通这个问题,StringBuilder的append方法没有改变字符串的引用地址,只是把其值改变了,为什么加了append返回的是true,没有加append却是false呢?如果在后面多加几个append返回的也是true。
也希望有人可以解答一下这个问题

相关推荐