在jdk7下慎用String.intern()做爲synchronized的對象鎖

有一段這樣的代碼:html

for (int i = 0; i < 10000000; i++) {
    ("bluedavy" + i).intern();
    if(i % 100 == 0)
    Thread.sleep(1);
}

你們能夠分別用這段代碼在JDK 6裏和JDK 7裏跑跑看看,會有什麼不一樣。java

上面的代碼在JDK 7裏執行時比JDK 6將會更多的觸發Young GC和Full GC,緣由請見這段描述:git

In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.apache

簡單來講就是在JDK 7裏String.intern生成的String再也不是在perm gen分配,而是在Java Heap中分配,所以天然上面的這段代碼在JDK 7裏會產生更爲嚴重的Young GC和Full GC,就像上面這段描述裏說的同樣,這個變化對於裝載了不少類的應用估計仍是會有些明顯的影響,對反射使用多的其實也會有些影響。api

關於這個變化,在Stack Overflow上還有個有趣的case:併發

class Test
{
 public static void main(String... args)
 {
  String s1="Good";
  s1=s1+"morning";
  System.out.println(s1.intern());
 
  String s2="Goodmorning";
  System.out.println(s1==s2);
 }
}

上面這段代碼在目前的JDK 6裏和JDK 7里居然會不一樣,JDK6裏會輸出false,而JDK 7會輸出true,緣由是JDK 6中執行String.intern時須要將此字符串的實例cp到perm並生成一個新的String對象,所以上面的s1和s2的對象地址是不一樣的,而在JDK 7中,執行String.intern時,則只是在String Pool中記錄此字符內容對應的字符串實例。oracle

儘管在比較字符串時,通常都不會用 == 去比較,但仍是要知道String.intern的這個變化。app

String.intern放進的String Pool是一個固定大小的Hashtable,默認值是1009,若是放進String Pool的String很是多,就會形成Hash衝突嚴重,從而致使鏈表會很長,而鏈表長了後直接會形成的影響就是當調用String.intern時性能會大幅降低(由於要一個一個找)。less

如今仔細想一想,看來當時這個case並非由於頻繁拋異常形成的,而是由於這個case中拋的是NoSuchMethodException,而拋這個異常的緣由是由於調用了Class.getMethod找方法沒找到,在class.getMethod這方法的實現裏會調用name.intern,而很不幸的是這個case裏傳入的name會根據請求而變,所以致使了String Pool中放入了不少的String,hash衝突嚴重,鏈表變長,從而才致使了形成了String.intern過程變得比較耗CPU。性能

JDK爲了解決這個問題,在6u32以及JDK 7的版本里支持了StringTable大小的配置功能,可在啓動參數上增長-XX:StringTableSize來設置,具體的信息見:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962930
不過目前JDK未提供方法來查看StringTable中各桶的鏈表長度,若是提供這個的話就更好了

 

以上內容轉載自:http://www.chepoo.com/jdk7-string-intern-change.html

 

瞭解String.intern()在jdk7的變化後,咱們爲了在單例類裏併發時對同一個用戶保證操做原子性,會加同步塊,例如:

  synchronized (("" + userId).intern()) {
            // TODO:something
   }

這個在jdk6裏問題不算大,由於String.intern()會在perm裏產生空間,若是perm空間夠用的話,這個不會致使頻繁Full GC,

可是在jdk7裏問題就大了,String.intern()會在heap裏產生空間,並且仍是老年代,若是對象一多就會致使Full GC時間超長!!!

 

慎用啊!解決辦法?終於找到了。

這裏要引用強大的google-guava包,這個包不是通常的強大,是徹底要把apache-commons*取締掉的節奏啊!!!

Interner<String> pool = Interners.newWeakInterner();

synchronized ( pool.intern("BizCode"+userId)){

//TODO:something

}

API文檔:http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Interners.html

代碼參考TEST類:https://chromium.googlesource.com/external/guava-libraries/+/release15/guava-tests/test/com/google/common/collect/InternersTest.java

 原理?折騰一下看看這個類的原碼吧~其實實現並不難,就是折騰而已~API上是這麼說的:

 Interners.newWeakInterner()

Returns a new thread-safe interner which retains a weak reference to each instance it has interned, and so does not prevent these instances from being garbage-collected. This most likely does not perform as well as newStrongInterner(), but is the best alternative when the memory usage of that implementation is unacceptable. Note that unlike String.intern(), using this interner does not consume memory in the permanent generation.

 

 這樣就能夠解決FULL GC問題了吧。效果如何?試試看。

厄.其實這樣也會使堆產生不少String,但應該能被回收掉吧.

相關文章
相關標籤/搜索