有一段這樣的代碼:
for (int i = 0; i < 10000000; i++) { ("bluedavy" + i).intern(); if(i % 100 == 0) Thread.sleep(1); }
大家可以分別用這段代碼在JDK 6里和JDK 7里跑跑看看,會有什么不同。
上面的代碼在JDK 7里執行時比JDK 6將會更多的觸發Young GC和Full GC,原因請見這段描述:
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.
簡單來說就是在JDK 7里String.intern生成的String不再是在perm gen分配,而是在Java Heap中分配,因此自然上面的這段代碼在JDK 7里會產生更為嚴重的Young GC和Full GC,就像上面這段描述里說的一樣,這個變化對於裝載了很多類的應用估計還是會有些明顯的影響,對反射使用多的其實也會有些影響。
關於這個變化,在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中記錄此字符內容對應的字符串實例。
盡管在比較字符串時,一般都不會用 == 去比較,但還是要知道String.intern的這個變化。
String.intern放進的String Pool是一個固定大小的Hashtable,默認值是1009,如果放進String Pool的String非常多,就會造成Hash沖突嚴重,從而導致鏈表會很長,而鏈表長了后直接會造成的影響就是當調用String.intern時性能會大幅下降(因為要一個一個找)。
現在仔細想想,看來當時這個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,但應該能被回收掉吧.