String的intern()详解

1.JDK中注释

String的intern()详解
返回字符串对象的canonical表示。由String类私有维护的字符串池,最初为空。
当调用intern方法时,如果池已经包含此字符串(equals确定),则返回池中字符串。否则,将此String对象添加到池中并且返回该String对象的引用。
对于任何两个字符串s和t,当且仅当s.equals(t)为真时,s.intern()==t.intern()才为真。
所有字符串字面值和字符串常量表达式都是intern。
返回值:与该String对象有相同内容的对象,保证来自字符串唯一的池。

从注释可以看出来,intern有俩种情况
1.如果存在
   判断存在内容是引用还是常量,
    如果是引用,
     返回引用地址指向堆空间对象,
    如果是常量,
     直接返回常量池常量
  2.如果不存在,
   将当前对象引用复制到常量池,并且返回的是当前对象的引用

2.intern在常量池中存储常量还是引用

1.在堆上创建对象,在常量池创建引用
String a = new String("A") + new String("B");//在堆上创建对象AB
// a.intern();//将该对象AB的引用保存到常量池上
System.out.println(a == a.intern());//true
2.在常量池创建常量
String b="abc";//在常量池中创建常量abc
String c="abc";//直接返回常量abc
System.out.println(b==c);
3.在堆上创建对象,在常量池上创建常量
 String a3 = new String("AA");//在堆上创建对象AA,在常量池中创建常量AA
4.对象是否共存

3.关于intern数据存储

它的大体实现结构就是: JAVA 使用 jni 调用c++实现的StringTable的intern方法,
StringTable的intern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。
要注意的是,String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009,如果放进String
Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。在jdk7中,StringTable的长度可以通过一个参数指定:
-XX:StringTableSize=99991 相信很多 JAVA 程序员都做做类似 String s = new String(“abc”)这个语句创建了几个对象的题目。
这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA
Heap中的 String 对象。

1.JDK8以后StringTable常量池默认大小

关于StringTableSize,使用虚拟机参数 -XX:+PrintFlagsInitial或者
-XX:+PrintFlagsFinal看看Java8设置的默认值:
uintx StringTableSize = 60013 {product}
链接法处理冲突:
template <MEMFLAGS F> inline void BasicHashtable<F>::add_entry(int index, BasicHashtableEntry<F>* entry) {
  entry->set_next(bucket(index));
  _buckets[index].set_entry(entry);
  ++_number_of_entries;
}

StringTable的intern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是60013 。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

4.分析与总结

Java7之后的字符串常量值和String.intern()机制:
1)对于字符串字面值和字符串常量表达式,都会intern,添加到常量池,例如new String(“1”);就有两个操作:新建堆对象String;将"1"添加到常量池
2)对于String s4 = "11"这种,会在字符串常量池中新建"11"对象
3)String.intern(),因为已经转移到了堆中,所以没有必要再在常量池存储一份对象拷贝,所以直接存储堆中String对象的引用。
intern()方法目的是提示JVM把相应字符串缓存起来,以备重复使用。使用JDK6这种历史版本,不推荐大量使用intern。因为缓存的字符串存在PermGen永久代里,这个空间是很有限的,也基本不会被FullGC之外的GC照顾到,所以如果使用不当,会出现OOM。
后续版本,该缓存放在堆中。并且JDK8中永久代被MetaSpace替代。
intern()是一种显式排重机制。副作用:
1)显式调用麻烦
2)很难保证效率,很难清楚地预计字符串重复情况
在Oracle JDK 8u20后,推出了一个新特性,G1 GC下的字符串排重。将相同数据的字符串指向同一份数据,是JVM底层的改变,并不需要Java类库做什么修改。
指定使用G1 GC,并开启-XX:+UseStringDeduplication。

上一篇:JVM笔记:Java虚拟机的常量池


下一篇:Oracle和MySQL数据库的备份与恢复