学习String.intern()过程中的疑问与解答

发现问题

在阅读周志明老师的《深入理解Java虚拟机》的P57的时候,有这样一个例子

1
2
3
4
5
6
7
8
9
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);
}
}

书中说到:这段代码在JDK1.6中运行会得到两个false,而在JDK1.7中运行,会得到一个true一个false。产生差异的原因是:在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永生代中,返回的也是永生代这个字符串实例的饮用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而JDK 1.7(以及部分其他虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。

此时不禁感到疑惑:“java”这个字符串在常量池中什么时候存在了?

解决问题

最开始猜测,java字符串会不会是默认加载的,一开始就在常量池,但是也只是猜测,也不知道为什么。

在一顿Google后,网上提示要去看System类的源代码

1
2
3
4
5
6
7
/* 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.
*/

源代码注释中我们可以知道:调用了initializeSystemClass方法,于是在该方法的源代码可以发现,该方法调用了sun.misc.Version.init()的静态方法。

于是在init方法的代码中我们找到了java字符串

在初始化Version类时,对其静态常量字段根据指定的常量值做默认初始化,所以”java”被加载到了字符串常量池中,修改上面代码使字符串值为上面常量中的任意一个都会返回false。

参考资料