字符串常量池到底保存的是字符串对象还是字符串对象的引用?
首先看一下intern()的注释
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class <code>String</code>.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this <code>String</code> object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this <code>String</code> object is added to the
* pool and a reference to this <code>String</code> object is returned.
* <p>
* It follows that for any two strings <code>s</code> and <code>t</code>,
* <code>s.intern() == t.intern()</code> is <code>true</code>
* if and only if <code>s.equals(t)</code> is <code>true</code>.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
String 两种使用方式方式:
1、直接使用双引号声明出来的String对象会直接存储在常量池中。
2、如果不是用双引号声明的String对象,可以使用String提供的intern方法。当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
看下面两段代码:
第一段代码
public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
打印结果是
jdk6 下false false
jdk7 下false true
jdk6
s的引用指向的是在堆中对象,因为 jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA Heap 区域是
完全分开的。因为code1 已经new String("1" )此时在堆中产生了一个对象,在Perm区的字符串常量池中也产生了"1"
对象,s4显示声明的时候发现字符串常量池中已经存在了就直接引用(此时指向了Perm区),而s是指向堆中的对象,即便s.intern()之后,
s引用堆中,s2引用指向Perm区,==又判断的是引用地址,所以肯定输出false
s3 和 s4同理, new String("1") + new String("1"); 会在堆中产生一个"11"对象(堆中两个对象相加还是放在堆中),在字符串常量池中
产生"1",
s3调用intern()后把"11"放到字符串常量池中,但是此时s3指向的是堆中,s4还是指向的Perm区,比较引用地址还是输出false
jdk7
jdk7以后将在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的 Perm 区的,Perm 区是一个类静态的区域,主要存储一些加
载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生
java.lang.OutOfMemoryError: PermGen space错误的。 所以在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的
Java Heap 区域了。
s 和 s2 是一样的,s指向堆中的对象,s2直接引用常量池的"1",引用地址比较还是输出false
重点说一下s3和s4:
为什么输出了true,是因为String s3 = new String("1") + new String("1"); 这时候产生了两个对象,一个
是s3指向的对象在堆上(该对象为"11"),此时在常量池中还生成了"1"对象,所以为两个对象。
在s3调用intern()后,会把s3引用的对象"11"地址返回给字符串常量池中(jdk7以后常量池既可以存放对象也可存放引用地址),
这时候s4 = "11"时候检测到有"11",就将存放在常量池中s3的的地址返回给s4,所以 s3 == s4 输出true
引用文章部分推荐参考:https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html