本文出自——可乐可乐可,作者主页链接:可乐可乐可的个人主页
早上看群友们在讨论,出现了Java字符串究竟在哪里的话题,粗略目睹虚拟机的菜鸡本人起了兴趣,研究了一下整理出来这个博文
字符串究竟存在哪里
这里总结了大家同意的说法:
如果使用常量的方式,该对象将被存储在常量池(永久代)
如果使用new的方式,该对象将被存储在堆
下面的代码揭示了情况:
使用常量的形式,为同一个对象
使用new的方式,为不同的对象
即使调用了常量创建的对象的方法,该对象也并不会发生什么变化:说明就是一个对象,而不是一组数组
public class TestString{
public static void main(String[] args){
String a="Aa";
String b="Aa";
System.out.println(a==b);
String c=new String("Aa");
System.out.println(b==c);
b.charAt(0);
String d=new String("Aa");
System.out.println(d==c);
System.out.println(b==a);
}
}
/**
true
false
false
true
Process finished with exit code 0
**/
复制代码
简单的规则就是这样了,希望浅显了解看到这里就足够了
但反观具体的实现,又会觉得奇妙,比如Java的字符串常量池,intern方法,下面详细说明
更深入一步的了解
在开始之前,各位先看这个String中神奇的方法
String.intem();
/**
* Returns a canonical representation for the string object.
*
* A pool of strings, initially empty, is maintained privately by the
* class String
.
*
* When the intern method is invoked, if the pool already contains a
* string equal to this String
object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this String
object is added to the
* pool and a reference to this String
object is returned.
*
* It follows that for any two strings s
and t
,
* s.intern() == t.intern()
is true
* if and only if s.equals(t)
is true
.
*
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* The Java™ Language Specification.
*
* @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();
复制代码
这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。
先记住这个奇妙的方法,建议你思考一下这个逻辑。
我们尝试着使用这个intern方法,并判断intern方法的作用
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);
}
复制代码
代码解释:
s与s1的目的是为了比较:当常量池存在这个值时,new方法产生的字符串 与 intern后再使用常量产生的字符串是否为一个对象。
s3与s4的目的是为了比较:当常量池不存在这个值时,new方法产生的字符串 与 intern后再使用常量产生的字符串是否为一个对象。
如果你有两个JDK环境:1.6与1.7
你会得到两个截然不同的答案:
JDK1.6: false false
JDK1.7: false true
然后了解一下JDK1.6与JDK1.7中常量池的区别
先看看JDK1.6
在JDK1.6中,常量池是一块独立的区域,jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA Heap 区域是完全分开的。
有点人可能不是很懂
这里再换种说法:
“String”类型的String,存放在常量池,同时常量池与Java堆是两个不同的部分。
也就是说,
存放在常量池和存放在堆,实现方式都不一样。
常量池默认4M,所以存在消耗尽,抛出java.lang.OutOfMemoryError: PermGen space
调用intern方法,字符串若不存在在常量池,则会在方法区新建一个并返回
来到JDK1.7
在JDK1.7中,取消了这个称为perm的区域,常量池就是堆,在常量池中的String对象和堆的String对象是存在与同一区域的。
在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。
为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。
也就是说:JDK1.7中,常量池被放在了堆中,于是我们再次调用一个 常量池不存在的字符串的intern方法,就不需要新建对象了。
总结:JDK1.7中,字符串的位置从方法区调整到了堆,执行intern时,若没有这个字符,将把当前的字符串放进常量池,而不是新建一个
问题发现
综合上述,上述的代码其实是为了对字符串对象是否转移了区域进行验证
在JDK1.6和JDK1.7中运行同样的代码,结果却会有不同
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);
}
复制代码
在JDK1.6中,常量池和堆是明显分隔的
于是我们可以推断:
s1与s2
s对象new出,于是在堆中
s2对象为常量,存在与常量池
s与s2存储的位置都不同,s!=s2
s3与s4
s3为两个字符串对象相加,调用了intern方法
但是存储位置都不同一个在堆里,一个在常量池,s3必然不同于s4
结果为false false
在JDK1.7中,由于不存在了perm这个特殊的区域
s对象new出,于是在堆中
s2对象为常量,存在与常量池
s与s2存储的位置都不同,s!=s2
对于s3与s4
s3对象值为“11”,但是是由两个"1"拼接,所以常量池中只会存在"1"而不会存在"11"(这点很重要)
s3intern方法,在常量池中生成了"11"。因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 “11” 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。
s4直接使用常量池"11",“11”这个对象其实就是s3
s3==s4
结果为true true
参考文章