案例
此案例是《深入理解java虚拟机》中的一个案例。
package javase;
public class StringPool58Demo {
public static void main(String[] args) {
String str1 = new StringBuilder("ha").append("llo").toString();
System.out.println(str1);
System.out.println(str1.intern());
System.out.println(str1 == str1.intern());
System.out.println();
String str2 = new StringBuilder("wo").append("rld").toString();
System.out.println(str2);
System.out.println(str2.intern());
System.out.println(str2 == str2.intern());
System.out.println();
String str3 = new StringBuilder("ja").append("va").toString();
System.out.println(str3);
System.out.println(str3.intern());
System.out.println(str3 == str3.intern());
}
}
问:为什么只有“java”打印的是false?
intern方法
是由native修饰的本地方法。
String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中这个字符串的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此引用。在JDK6或更早之前的HotSpot虚拟机中,常量池都是分配在永久代中,我们可以通过-XX:PermSize和-XX:MaxPermSize限制永久代的大小,即可间接限制其中常量池的容量。
为什么
有一个初始化的java字符串是JDK中自带的,它是rt.jar中sun.misc.Version类中的一个字符串。在类加载的时候就已经在常量池中了。
也就是说,由根加载器加载到常量池中了。
这段代码在JDK6中运行,会得到两个false,而在JDK7中运行,会得到一个true和一个false。产生差异的原因是,在JDK6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder创建的字符串对象实例在Java堆上,所以必然不可能是同一个引用,结果将返回false。
而JDK7的intern()方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。而对str2比较返回false,这是因为“java”这个字符串在执行StirngBuilder之前就已经出现过了,字符串常量池中已经有它的引用,不符合intern()方法要求“首次遇到”的原则。
sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化,而在初始化时它需要对静态常量字段根据指定的常量值做默认初始化,此时被sum.misc.Version.launcher静态常量字段所引用的“java”字符串字面量就被intern到HotSpot VM的字符串常量池——StringTable里了。