在《深刻理解Java虚拟机》书中,提到在jdk1.7的版本中用String.intern()返回引用。java
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
书中给的解释是:app
JDK 1.7(以及部分其余虚拟机,例如JRockit)的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,所以intern()返回的引用和由StringBuilder建立的那个字符串实例是同一个。对str2比较返回false是由于“java”这个字符串在执StringBuilder.toString()以前已经出现过,字符串常量池中已经有它的引用了,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,所以返回true。
如今的疑问是“java”这个字符串在常量池中何时存在了?
我最开始的猜测是“java”这个字符串是否是常驻在常量池中的?那为何常驻在常量池中呢?Java虚拟机何时加载了“java”这个字符串?ide
最开始觉得是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类ui
/* 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”.this
先运行这个代码 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。
也但愿有人能够解答一下这个问题code