jvm之String常量池的優化

jopen 9年前發布 | 18K 次閱讀 JVM Java開發

原文  http://wangxinchun.iteye.com/blog/2190841

String.intern() 方法會自動把String放到jvm的PSPermGen的常量區。

關于String.intern()的使用需要注意以下兩點:

1、對于日常工作中List中的數據對象,如果對象的某個屬性是String,比如性別,國家等重復率較高的字符串取值,如果放入常量區,會節省大量的內存空間。

2、jvm的常量池的的搜索比較慢,速度甚至比ConcurrentHashMap 慢了不少。

證明:

public static long  times = 10000000L;
public static void testIntern() {
    System.gc();
    List<String> list = new ArrayList<String>();
    long l = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
      list.add(("A" + (i % 1000)).intern());
    }
    long ll = System.currentTimeMillis();
    System.out.println("testIntern time :" + (ll -l));
    System.gc();
    System.out.println("testIntern:"
        + (wrapM(Runtime.getRuntime().totalMemory()) - wrapM(Runtime
            .getRuntime().freeMemory())));

  }


結果:

testIntern time :2657

testIntern memory:1.8076171875M

eden space 86272K, 3% used [0x00000000f9c00000,0x00000000f9f4b3f0,0x00000000ff040000)

public static void testCommon() {
    System.gc();
    List<String> list = new ArrayList<String>();
    long l = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
      list.add(("A" + (i % 1000)));
    }
    long ll = System.currentTimeMillis();
    System.out.println("testIntern time :" + (ll -l));
    System.gc();
    System.out.println("testCommon memory:"
        + (wrapM(Runtime.getRuntime().totalMemory()) - wrapM(Runtime
            .getRuntime().freeMemory())));
  }

結果:

Exception in thread "main" [Full GC [PSYoungGen: 81920K->0K(92160K)] [PSOldGen: 102400K->188K(102400K)] 184320K->188K(194560K) [PSPermGen: 3048K->3048K(21248K)], 0.0530156 secs] [Times: user=0.05 sys=0.00, real=0.05 secs]

java.lang.OutOfMemoryError: GC overhead limit exceeded

at java.lang.StringBuilder.toString(StringBuilder.java:430)

at com.mystore.core.common.TestMemory.testCommon(TestMemory.java:85)

at com.mystore.core.common.TestMemory.main(TestMemory.java:13)

public static void testCurrentHashMap() {
    System.gc();
    List<String> list = new ArrayList<String>();
    long l = System.currentTimeMillis();
    for (int i = 0; i < times; i++) {
      list.add((StringCache.get("A" + (i % 1000))));
    }
    long ll = System.currentTimeMillis();
    System.out.println("testIntern time :" + (ll -l));
    System.gc();
    System.out.println("testCurrentHashMap memory:"
        + (wrapM(Runtime.getRuntime().totalMemory()) - wrapM(Runtime
            .getRuntime().freeMemory())));

  }

  private static double wrapM(long length) {
    return length / 1024 / 1024.0;
  }
static class StringCache {
    private static ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>(10000);
    public static String get(String str){
      if(null == str){
        return null;
      }

      String ret = map.get(str);
      if(null == ret){
        map.putIfAbsent(str, str);
        ret = map.get(str);
      }
      return ret;
    }

  }

結果:testIntern time :2006

testCurrentHashMap memory:1.9482421875

eden space 85888K, 3% used [0x00000000f9c00000,0x00000000f9f477f8,0x00000000fefe0000)

結論:

對比testCommon 和 testIntern 說明testCommon 會占用較多的堆區內存,testIntern 會導致常量區會有微量的增長(僅僅1000個字符常量而已)

對比testIntern 和 testCurrentHashMap ,testCurrentHashMap 在性能方面有優勢,更為需要關注的是testCurrentHashMap的內存分配在了堆區,而testIntern 分配在了常量區,一般情況下 堆區的老年代要比持久代要大的多,所以從gc的角度來說,更應該使用testCurrentHashMap 的方式。不好的一點是 testCurrentHashMap 中的常量會一直增長沒有過期 策略,而常量池則會在full gc 的時候自動做清理。testCurrentHashMap 優化的方向是 使用帶緩存并且線程安全的Map,比如guava的緩存Map

參考: 性能對比參見 http://stackoverflow.com/questions/10624232/performance-penalty-of-string-intern

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!