下面针对手工池的相同测试:
  0;manual time=0.001 sec
  50000;manual time=0.03 sec
  100000;manual time=0.034 sec
  150000;manual time=0.008 sec
  200000;manual time=0.019 sec
  250000;manual time=0.011 sec
  300000;manual time=0.011 sec
  350000;manual time=0.008 sec
  400000;manual time=0.027 sec
  450000;manual time=0.008 sec
  500000;manual time=0.009 sec
  550000;manual time=0.008 sec
  600000;manual time=0.008 sec
  650000;manual time=0.008 sec
  700000;manual time=0.008 sec
  750000;manual time=0.011 sec
  800000;manual time=0.007 sec
  850000;manual time=0.008 sec
  900000;manual time=0.008 sec
  950000;manual time=0.008 sec
  1000000;manual time=0.008 sec
  当JVM有足够内存时,手工编写的池提供了良好的性能。不过不幸的是,我的测试(保留String.valueOf(0<N<1,000,000,000))保留非常短的字符串,在使用-Xmx1280M参数时它允许我保留月为2.5M的这类字符串。JVM字符串池(size=1,000,003)从另一方面讲在JVM内存足够时提供了相同的性能特性,知道JVM字符串池包含12.72M的字符串并消耗掉所有内存(5倍多)。我认为,这非常值得你在你的应用中去掉所有手工字符串池。
  在Java 7u40+以及Java 8中的String.intern()
  Java7u40版本扩展了字符串池的大小(这是组要的性能更新)到60013.这个值允许你在池中包含大约30000个独立的字符串。通常来说,这对于需要保存的数据来说已经足够了,你可以通过-XX:+PrintFlagsFinal JVM参数获得这个值。
  我尝试在原始发布的Java 8中运行相同的测试,Java 8仍然支持-XX:StringTableSize参数来兼容Java 7特性。主要的区别在于Java 8中默认的池大小增加到60013:
  50000;time=0.019 sec
  100000;time=0.009 sec
  150000;time=0.009 sec
  200000;time=0.009 sec
  250000;time=0.009 sec
  300000;time=0.009 sec
  350000;time=0.011 sec
  400000;time=0.012 sec
  450000;time=0.01 sec
  500000;time=0.013 sec
  550000;time=0.013 sec
  600000;time=0.014 sec
  650000;time=0.018 sec
  700000;time=0.015 sec
  750000;time=0.029 sec
  800000;time=0.018 sec
  850000;time=0.02 sec
  900000;time=0.017 sec
  950000;time=0.018 sec
  1000000;time=0.021 sec
  测试代码
  这篇文章的测试代码很简单,一个方法中循环创建并保留新字符串。你可以测量它保留10000个字符串所需要的时间。好配合-verbose:gc JVM参数来运行这个测试,这样可以查看垃圾收集是何时以及如何发生的。另外好使用-Xmx参数来执行堆的大值。
  这里有两个测试:testStringPoolGarbageCollection将显示JVM字符串池被垃圾收集—检查垃圾收集日志消息。在Java 6的默认PermGen大小配置上,这个测试会失败,因此好增加这个值,或者更新测试方法,或者使用Java 7.
  第二个测试显示内存中保留了多少字符串。在Java 6中执行需要两个不同的内存配置比如:-Xmx128M以及-Xmx1280M(10倍以上)。你可能发现这个值不会影响放入池中字符串的数量。另一方面,在Java 7中你能够在堆中填满你的字符串。
  /**
  -Testing String.intern.
  *
  -Run this class at least with-verbose:gc JVM parameter.
  */
  public class InternTest{
  public static void main(String[]args){
  testStringPoolGarbageCollection();
  testLongLoop();
  }
  /**
  -Use this method to see where interned strings are stored
  -and how many of them can you fit for the given heap size.
  */
  private static void testLongLoop()
  {
  test(1000*1000*1000);
  //uncomment the following line to see the hand-written cache performance
  //testManual(1000*1000*1000);
  }
  /**
  -Use this method to check that not used interned strings are garbage collected.
  */
  private static void testStringPoolGarbageCollection()
  {
  //first method call-use it as a reference
  test(1000*1000);
  //we are going to clean the cache here.
  System.gc();
  //check the memory consumption and how long does it take to intern strings
  //in the second method call.
  test(1000*1000);
  }
  private static void test(final int cnt)
  {
  final List<String>lst=new ArrayList<String>(100);
  long start=System.currentTimeMillis();
  for(int i=0;i<cnt;++i)
  {
  final String str="Very long test string,which tells you about something"+
  "very-very important,definitely deserving to be interned#"+i;
  //uncomment the following line to test dependency from string length
  //final String str=Integer.toString(i);
  lst.add(str.intern());
  if(i%10000==0)
  {
  System.out.println(i+";time="+(System.currentTimeMillis()-start)/1000.0+"sec");
  start=System.currentTimeMillis();
  }
  }
  System.out.println("Total length="+lst.size());
  }
  private static final WeakHashMap<String,WeakReference<String>>s_manualCache=
  new WeakHashMap<String,WeakReference<String>>(100000);
  private static String manualIntern(final String str)
  {
  final WeakReference<String>cached=s_manualCache.get(str);
  if(cached!=null)
  {
  final String value=cached.get();
  if(value!=null)
  return value;
  }
  s_manualCache.put(str,new WeakReference<String>(str));
  return str;
  }
  private static void testManual(final int cnt)
  {
  final List<String>lst=new ArrayList<String>(100);
  long start=System.currentTimeMillis();
  for(int i=0;i<cnt;++i)
  {
  final String str="Very long test string,which tells you about something"+
  "very-very important,definitely deserving to be interned#"+i;
  lst.add(manualIntern(str));
  if(i%10000==0)
  {
  System.out.println(i+";manual time="+(System.currentTimeMillis()-start)/1000.0+"sec");
  start=System.currentTimeMillis();
  }
  }
  System.out.println("Total length="+lst.size());
  }
  }
  总结
  由于Java 6中使用固定的内存大小(PermGen)因此不要使用String.intern()方法
  Java7和8在堆内存中实现字符串池。这以为这字符串池的内存限制等于应用程序的内存限制。
  在Java 7和8中使用-XX:StringTableSize来设置字符串池Map的大小。它是固定的,因为它使用HashMap实现。近似于你应用单独的字符串个数(你希望保留的)并且设置池的大小为接近的质数并乘以2(减少碰撞的可能性)。它是的String.intern可以使用相同(固定)的时间并且在每次插入时消耗更小的内存(同样的任务,使用java WeakHashMap将消耗4-5倍的内存)。
  在Java 6和7(Java7u40以前)中-XX:StringTableSize参数的值是1009。Java7u40以后这个值调整为60013(Java 8中使用相同的值)
  如果你不确定字符串池的用量,参考:-XX:+PrintStringTableStatistics JVM参数,当你的应用挂掉时它告诉你字符串池的使用量信息。