关于String.intern()
方法,这个问题都被问烂了,有的文章在分析的时候还在用jdk1.7
,jdk1.8
之后内存模型发生了变化,内存的变化也会影响intern方法的执行,这里有必要写文章分析一下,请大家务必从头开始看,这样才能搞懂
1.字符串常量池划分
jvm对字符串常量池在不同jkd版本有不同的划分,这里用hotspot
来分析,文章后部分会使用,主要有以下三种方式
大致划分为这几个部分,对方法区,元空间和堆等概念模糊的朋友可以参考方法区,永久代,元空间这篇文章
2.回顾string对象在内存中的位置以及不可变性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
字符串是不可变的,public final class String
字符串是利用其内部value字符数组
来存储的
表示字符串的字符数组也是不可变的:private final char value[];
简要分析一下:
str1
和str2
都是在栈上创建的字符串引用,不同的是str2
是通过new
关键字创建的在堆上的String
对象,str1
直接创建的是字面量abc
,str2
通过在堆上创建String
对象,而string
对象的内部属性value数组
是一个对象引用,指向常量池中的abc
,常量池中的abc也可以理解为是一个string
类中不可变的字符数组
3.intern()
有了上面的预备知识,我们开始分析intern()
这个方法,这里列出网上比较热门的代码来分析:
public static void main(String[] args) {
String s1 = new String("1");
s1.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中返回false,false,jdk1.7是false,true
在我们开始分析之前,我们回到第一步,先对jvm内存划分有一个了解
JDK1.6以后常量池被放置在了堆空间,JDK1.8又将常量池划分回方法区,这里的方法区的实现是元空间
jdk1.6实现方式
通过图片分析:
String s3 = new String("1") + newString("1")
,这行代码在字符串常量池中生成“1” ,并在堆空间中生成s3引用指向的对象(内容为"11")。注意此时常量池中是没有 “11”对象的。
s3.intern()
,这一行代码,是将 s3中的“11”字符串放入 String 常量池中,此时常量池中不存在“11”字符串,JDK1.6的做法是直接在常量池中生成一个 “11” 的对象。
但是在JDK1.7
中,常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用直接指向 s3 引用的对象,也就是说s3.intern() ==s3会返回true。
String s4 = "11"
, 这一行代码会直接去常量池中创建,但是发现已经有这个对象了,此时也就是指向 s3 引用对象的一个引用。因此s3 == s4返回了true。
jdk1.7实现方式
总结
虽然jdk1.7以后将常量池转移到了方法区中,但intern的工作原理却并没有改变,说到这里,我们再来通过一个案例来检验一下你是否真的理解了intern
的作用
String s1 = new String("1");
String s2 = "1";
s1.intern();
System.out.println(s1 == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
答案是:
false
false
- s1已经在常量池中创建了"1",
s1.intern();
的作用是徒劳的,返回的是堆中的地址,而s2返回的是常量池中的地址,结果当然为false
- s4抢先一步在常量池中创建了字面量"11",并且s3只在常量池中创建了"1",所以
s3.intern()
返回的是常量池的"11"的地址,s3当然不等于s4,返回false