在虚拟机规范中,运行时常量池(包含字符串常量池)是方法区的一部分。在HotSpot虚拟机中,JDK1.6以及之前,使用永久代来实现方法区,所以字符串常量池是在永久代中,在JDK1.7及其以后,字符串常量池被放在堆中实现,String:intern()方法是一个本地方法。当执行字符串的intern()方法后,对不同的字符串赋值方式会有不同的效果,主要有以下两种方式:
第一种:
String s1= new String(“我是new出来的对象”);
Stirng s2=s1.intern();
第二种:
String s3 = “我是常量字符串”;
String s4 = s3.intern();
在分析这两种情况之前,我们从虚拟机规范层面分析一下new String()行为,当执行以下语句时,我们看看发生了什么?
String s1Test =new String(“我是ne出来的对象时”);---------------------1
String s2Test = “我是字符串字面量”; ---------------------2
执行1虚拟机会在堆上创建一个对象,然后将栈中的s1变量(该对象的引用)指向该对象。当直接用双引号赋值时(执行2),将栈中的s2Test变量指向常量池中该字符串的引用如下图所示
下面我们来分析一下上述两种情况在JDK1.6和1.7及其之后的区别
先看第一种情况在JDK1.6中的实际处理方式:
String s1= new String(“我是new出来的对象”);
Stirng s2=s1.intern();
当执行String s1= new String(“我是new出来的对象”);会在堆中建立一个对象,同时在字符串常量池中生成一个对象,然后将s1指向堆中new出来的对象的实例。
当执行String s2 = s.intern();
1.如果发现字符串常量池中有该对象,在常量池中生成一个该字符串实例对象的引用,然后返回常量池中该实例对象的引用。
2.如果字符串常量池中有该对象的引用,直接返回常量池中对象的引用
再看第二种情况在JDK1.6中的实际处理方式:
String s3 = “我是常量字符串”;
String s4 = s3.intern();
执行String s3 = “我是常量字符串”;首先检查字符串常量池中有没有该字符串的引用,如果有直接返回,如果没有,在常量池中生成一个该字符串对象,返回指向该对象的引用。并将s3指向常量池中的该字符串的引用。
执行String s4 = s3.intern();由于字符串中已经有了一个字符串引用,所以直接返回。如下图所示:
看完JDK1.6中的处理方式,我们再来看上述两种情况在JDK1.7及其之后的处理方式
在JDK1.7中第一种情况:
String s1= new String(“我是new出来的对象”);
Stirng s2=s1.intern();
当执行String s1= new String(“我是new出来的对象”);会在堆中建立一个对象,然后将s1指向堆中new出来的对象的实例。
当执行String s2 = s.intern();
1.如果发现字符串常量池中没有该对象的引用,堆中有该对象的实例,然后在常量池中生成个该对象的引用,将该对象的引用指向堆中的实例,与堆中的对象共用一个实例,原因是在JDK1.7及其以后,将字符串常量池放到了堆中去实现,既然堆中有了一个对象,没必要在建立一个。
2.如果字符串常量池中有该对象的引用,直接返回常量池中对象的引用。
再看第二种情况在JDK1.7中的实际处理方式:
String s3 = “我是常量字符串”;
String s4 = s3.intern();
执行String s3 = “我是常量字符串”;首先检查字符串常量池中有没有相同的字符串对象的引用,如果有直接返回,如果没有,在常量池中生成一个该字符串对象。返回该对象的引用。
执行String s4 = s3.intern();由于字符串中已经有了一个字符串引用,所以直接返回。如下图所示:
所以通过上述分析,HotSpot虚拟机在jdk1.7及其以后中,在new String()对象时,对象生成策略发生了改变,不需要在常量池中缓存一个对象实例的副本,而是直接在堆中建立一个对象,调用intern()方法时,只需在常量池中建立一个指向该实例的引用即可。所以调用intern()方法,无论是堆中还是常量池中,实例对象只有一个。这是因为HotSpot虚拟机在jdk1.7及其之后,将字符串常量池放在了堆中去实现。