有一段这样的代码: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,但应该能被回收掉吧.