在JDK6中,reflection的并发性能其实不是很好,我们就遇到过在高并发环境下反射性能急剧下降的情况。大规模web应用就是有这么大并发呀,而且因为框架或其他原因,广泛使用reflection,所以值得重视这种问题。
继续阅读前建议打开JDK源代码。(在Eclipse中只需在Preferences->Installed JREs里把JRE设为你安装的JDK,而不是默认那个,然后Ctrl+Shift+T就可以用名字查找JDK的任意一个类了)
1. ClassLoader
首先我们来看看Class.forName(String)这个方法,它在反射中使用之频繁,如大白菜一般。
Class.forName(String)只是调用了forName0(...)方法,这是个native方法,C++实现,看不到源代码。源代码
其实它会调用ClassLoader.loadClass(String),然后转到ClassLoader.loadClass(String,
boolean)。源代码
在JDK6中,这是一个synchronized方法,这个方法做什么详见上面链接的源代码。它会对整个ClassLoader加锁,可想而知——整个系统都在这等待——扛不住高并发。
在JDK7中,这里得到了优化(感谢RednaxelaFX大大的提示)。源代码
该方法不再是synchronized,其做法是把整个方法体包在“synchronized
(getClassLoadingLock(name))”中。这个name,是类的全限定名。它维护了一个map,key为类名,
value为一个普通的Object(当做锁对象来用)。所以锁粒度一下子从ClassLoader类级降到了每个class的级别。听RednaxelaFX说JDK8还有进一步优化。
2. Proxy
Proxy技术在AOP中密切使用。广泛使用的有JDK Proxy和CgLib Proxy两种实现。
我们可以看一下JDK7的Proxy类:源代码
Proxy.getProxyClass(...)会转到Proxy.getProxyClass0(...)。然后一路往下看,出现了两次同步:
1) synchronized (loaderToCache) 源代码
这个loaderToCache存放了各个classLoader各自的class
cache,key=classLoader,value=map<classLoader, map<className,
class>>。
中间做的操作挺轻量的,但是锁粒度这么大,整个系统在这等待,扛不住高并发。
我建议两个改法:
第一个改法是去掉synchronized,把loaderToCache改成ConcurrentHashMap,目的不是减小同步范围,而是减小锁粒度,不对整个cache加锁,而只对map的一部分加锁(由于ConcurrentHashMap的实现),高并发时的性能会明显改善。
第二个改法是loaderToCache里面不直接存放每一个cache,而是把cache包装在一个简单的单元素容器里,对单个容器加锁,代码示例:
cacheContainer = loaderToCache.get(loader); synchronized (cacheContainer) { cache = cacheContainer.get() if (cache == null) { cache = new HashMap<>(); cacheContainer.set(cache); } }
2) synchronized (cache) 源代码
这个cache是class cache,key=className,
value=class。
哎哟妈呀,也给整个cache加着锁哩!虽然锁范围很小,wait调用还会释放锁,但是你要让大家都为了一个类在这等待吗?
改法不多说了,与前面所述的类似。采用第二个改法稍微复杂点,因为当proxy
generating失败时会remove相应的key,所以建议尽量不要remove吧,或者只对remove做同步。
另外还能看到Proxy.proxyClasses这个属性是个synchronized WeakHashMap,要是WeakHashMap也有个concurrent实现就好了。
总结下来就是,JDK6 reflection的并发性能不是很高,JDK7有明显改善(JDK8进一步改善),况且JDK6离XP的命运不远了,大家还没升级的都赶紧升级吧(没错,就是你们这些大公司!)。至于Proxy,可以用CgLib的(字节码生成慢一些,但生成之后用起来就快多了)。
最后建议大家以后不要使用synchronized Map了,用ConcurrentHashMap吧,由于锁粒度的细化,高并发时的性能好很多。Spring框架就在2013年上半年把所有关于map的synchronization统统换成了ConcurrentHashMap。那时我本来想捡个漏赚个pull request,晚了一步啊。
JDK reflection的并发问题,布布扣,bubuko.com
原文:http://www.cnblogs.com/sorra/p/3653951.html