字符串池用于存储独立共享的String对象,此对象将代替其他等值的String对象. 你也可以通过维护一个自定义的Map<String, String>(可以根据需求,通过软引用或弱引用实现)来实现字符串池的功能, 并以Map的value作为标准值. 同时, 你也可以利用JDK提供的String.intern()方法. 由于字符串池操作失去控制时, 非常容易导致OutOfMemoryException, Java 6中的String.intern()已经被很多标准禁用. Oracle Java 7中, 对于字符串池的实现做了很大程度的修改. 详情可以参见: bug_6962931 和 bug_6962930.
在过去, 所有的标准化字符串都被存在内存永久代中(堆内存中一块固定区域, 用于存储已经加载的class信息与字符串池). 除了显式的标准化字符串, 永久代字符串池也包含所有在程序中用到的常量字符串(如果累或方法从未被加载或调用, 任何常量都不会被加载)
Java 6中此种类型的字符串池最大的问题就是在永久代中的定位. 永久代中含有一个固定大小并且无法在运行时扩展的内存区域.你可以通过-XX:MaxPermSize=N来进行设置. 据我所知, 根据运行平台的不同, 默认的永久代的大小在32M至96M之间不等. 你可以增加它的大小, 但是它的大小仍旧是固定的. 因此, 这个限制要求对于String.intern的应用非常谨慎(最好不要在任何不可控的用户输入上应用此方法). 这就是为什么很多场景下, Java 6中的字符串池会通过手动管理的map来进行重新实现.
Oracle的工程师们对于Java 7中的字符串池做了相当显著的改动(字符串池被重新定位到了堆中). 这意味着已经不再被一块独立固定大小的内存区域所限制. 所有的字符串都存储在堆内存中, 同大多数对象一样, 你可以通过设置堆存内大小来实现程序的调优.技术层面上, 这可能是让你重新启用String.intern()的最显著的原因了.不过, 还是有一个其他原因的.
是的, 如果JVM字符串池中的字符串没有了任何引用, 它就具备了被垃圾回收的资格. 这种行为适用与所有版本的Java. 这意味着如果标准化字符串超出了作用域并且没有任何引用, 它就会从JVM字符串池中被垃圾回收.
对于字符串, 具备垃圾回收的资格并存储在堆内存中, JVM字符串池似乎是存放所有字符串的最佳场所. 理论上这是正确的,无用的字符串会被垃圾收集, 已用的字符串会被保存在内存中, 这样你就可以在输入中得到一个等值的字符串. 似乎这是一个完美的存储策略? 基本如此. 在做出任何决定之前, 你必须了解字符串池是如何被实现的.
字符串池的实现方式是一个固定容量的HashMap, Map中的每个单元包含一个拥有相同哈希码的字符串的list. 具体实现可以参考: bug_6962930
池的大小默认为1009(这是上诉BUG报告中源码中的值, 在Java7u40已被增大). 在早期Java6的版本中, 这个值是一个常量,在Java6u30和Java6u41中实现可以配置化. 在Java7的早期版本中就实现了可配置化(至少Java7u02中是可配置的). 配置通过指定-XX:StringTableSize=N实现, N代表字符串池Map的大小.为了更好的性能, 请保证他是质数.
这个参数在Java6中不会起到什么作用, 因为仍会被永久代的固定大小限制. 进一步的探讨将会不包含Java 6
Java7中, 换句话说, 会被更大的堆内存限制. 这意味着可以将字符串池大小设置为一个相当大的值(具体值应参考应用的需求). 作为一个规则, 当内存中数据大小增长到几百兆时, 需要开始考虑内存消耗问题. 在这种情况下, 为字符串池分配可以容纳一百万个元素的8-16兆的空间似乎是一个合理折衷的做法(配置-XX:StringTableSize时, 不要使用1,000,000,因为他不是质数, 建议使用1,000,003).
字符串池的大小在Java7u40之后被提升至60013(这是一个主要的性能调整). 这个大小可以容纳大约30000个不同的字符串. 对于值得标准化的字符串来说, 这是一个显著的提升. 你可以通过设置XX:+PrintFlagsFinal来获取这个值.