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。
也希望有人可以解答一下这个问题