intern()
intern():返回字符串对象的规范表示
- 方法调用
intern()
的时候,如果常量池中找的到,就直接返回引用 - 如果没有找到就添加到常量池中,并且返回引用
- 只有
s.equals(t)
为true,s.intern() == t.intern()
才是true
intern()的大体结构是:Java使用 JNI(Java Native Interface)调用C++实现的StringTable
的intern
方法,StringTable
的intern
方法跟Java中的HashMap
的实现差不多,只是不能自动扩容,默认大小是1009.
- String的String Pool是一个固定大小的HashTable,默认值大小长度是1009.
- String Pool的String非常多,会造成Hash冲突严重,从而导致链表会很长,而链表长了会直接造成影响就是,调用
String.intern
性能大幅下降
- String Pool的String非常多,会造成Hash冲突严重,从而导致链表会很长,而链表长了会直接造成影响就是,调用
- jdk6中,
StringTable
是固定的,长度是1009 - jdk7中,
StringTable
的长度可以扩展,使用参数:-XX:StringTableSize=99991
题目样例
String s = new String("abc")
:这条语句创建了两个对象
- 第一个对象是“abc”字符串存储在常量池中
- 第二个对象在Java Heap中的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);
}
打印结果:
- JDK 6 下
false false
- JDK 7 下
false true
在JDK 6中,常量池是放在Perm区中的,Perm区和Java Heap区域是完全分开的,由于直接用引号“”
声明的字符串都是会直接在字符串常量池中生存,而new出来的String对象是放在Java Heap区域,所以区域不同,比较也就肯定不同,即使调用String.intern
方法也是没有任何关系。
在JDK 7中,字符串常量池已经移到了Java Heap区域,而在JDK 8已经取消了Perm区域,而建立了元区域(MetaSpace)。
由于String s = new String("1");在堆中一个和常量池中一个,执行s.intern();还是在堆中,
再来看代码,String s3 = new String("1") + new String("1");
,这条语句生成了两个对象,一个事字符串常量池中的“1”
,另一个是 Java Heap 中的s3引用指向的对象,内容是"11"
。
s3.intern();
这句代码呢,因为常量池中不存在“11”
字符串,所以把“11”
字符串放入了 String 常量池中,符合JDK 6中的做法,但是在JDK 7 中常量池是不在Perm区域(方法区)中的,也就是说不需要再存储一份对象了,可以直接引用堆中的引用,这份指向s3引用的对象,也就是说引用地址是相同的。
是什么是常量池呢?
- 运行时常量池(Runtime Constant Pool)是方法区的一部分。
- Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table)
- 常量池表:用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区运行常量池中去。
- Java虚拟机对于Class文件每一部分格式都有严格的规定,但是对于运行时常量池,《Java虚拟机规范》没有做任何细节的要求,不同提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。
- 一般来说,除了保存Class文件描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储到运行时常量池中。
- 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定编译期才能产生
- 也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用比较多的便是String类的intern()方法。
什么是方法区呢
- 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
- 虽然在《Java虚拟机规范》把方法区描述为堆的一个逻辑部分,但是方法区还有个别名“非堆”(Non-Heap),目的是与Java堆区分开。
- 在JDK8以前,许多人习惯把方法区称之为永久代(Permanent Generation),或者两者混为一谈。本质上两者并不是等价的,因为仅仅在当时HotSpot虚拟机设计团队,选择把收集器的分代设计扩展至方法区,或者说是永久代来实现方法区而已,这样使得HostSpot垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码工作。
- JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了 JDK 8,终于完全废弃了永久代的概念,在本地内存中实现的元空间(Metaspace)来代替
- 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说回收效果难以令人满意,卸载效果更是苛刻。
- 根据《Java虚拟机规范》,方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
参考文章
1、https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html
2、《深入理解Java虚拟机》