通过String.intern(),值相同的字符串们在JVM里可以只占用一个String对象,从而减少内存的消耗。 另外程序使用过的String literal和String类型的常量也会被intern。

“intern”在这里是“拘留”的意思,

n. 实习生,实习医师
vt. 拘留,软禁
vi. 作实习医师

String.intern("foo")就是把字符串“foo”“拘留”在String类内部的一个pool里。

String pool实际上是一个固定大小的哈希表。 在Java 7之后,String pool占用的是堆(heap)内存。

因为哈希表是固定大小的,不会自动扩容,所以大小的设置很重要。 如果相比于要intern的字符串数目,哈希表的大小设置得过小的话,哈希表的性能就可能退化成线性搜索。

# 在已经intern了1000000个字符串的情况下,再intern 10000个字符串
# pool的大小是60013,会有很多collision,哈希表性能退化
time = 1.913 sec
# pool的大小是100003
time = 0.012 sec

和普通的HashMap不同的是,String pool里的字符串如果没有地方引用,可以被垃圾回收。 从这个方面看,String pool的行为和WeakHashMap<String, WeakReference<String>>差不多。 但是WeakHashMap<String, WeakReference<String>>消耗的内存是String pool的实现的5倍。

Java7u40之后String pool的默认大小是60013。 可以通过-XX:StringTableSize=N选项,给pool指定合适的大小。 考虑到哈希表的性能,pool的大小最好是质数,比如60013。 同时为了避免冲突(collision),pool的大小可以设成比“要intern的字符串数目X2”大一点的质数。

Java 6的时代,String pool放在PermGen区,所以intern太多字符串会导致PermGen区out of memory。

另外,选项-XX:+PrintStringTableStatistics可以打印出String pool的使用情况。 选项-XX:+PrintFlagsFinal可以显示String pool的大小。