公司当年有一个自己缓存集群用户session的Java library,是基于hazlcast2.0.3实现的,最近在customer site集群环境中某个blade报了Out of Memory Exception, 其他blades都正常,马上用jrockit jrcmd命令dump了堆和线程进行分析。
printf “##################### heap ##################\n” su -p occas -c "/opt/jrockit-jdk/bin/jrcmd `ps -ewf | grep weblogic.Name=traffic | grep -v grep | awk '{print $2}'` heap_diagnostics " printf “#####################threads ##################\n”su -p occas -c "/opt/jrockit-jdk/bin/jrcmd `ps -ewf | grep weblogic.Name=traffic | grep -v grep | awk '{print $2}'` print_threads"
用eclippse memory analyer打开dump回来的heap文件,打开Dominator Tree,发现hazelcast的ConcurrentMapManager吃掉了大部分的系统内存,其持有的CMap包含的java.util.concurrent.ConcurrenthashMap hashentry的数目无比巨大(近80w), 如图
很明显OOM是因为HashEntry有进无出,为什么会有进无出呢?我们先看看hazlcast这方面的清除HashEntry的代码,代码中hazelcast的从CMap里面clear数据有2个方法 mapRecords:removeAndPurgeRecord 和purgeIfNotOwnedOrBackup. 它们只有一个调用端:startCleanup function. 如下图所示:
查看startCleanup的实现,很luck,hazelcast每次清除数据的操作都会记录时间戳
final long now = System.currentTimeMillis(); long dirtyAge = (now - lastCleanup); boolean shouldRun = forced || (store != null && dirty && dirtyAge >= writeDelayMillis) || (dirtyAge > cleanupDelayMillis); if (shouldRun && cleanupActive.compareAndSet(false, true)) { <strong>lastCleanup = now;</strong>回到memory analyzer里面查看cmap的lastCleanup属性,诡异的发现时间停止在了1391476828844,用转换工具可知这个时间是: Tue, 04 Feb 2014 01:20:28 GMT.
现在我们知道hazelcast的cmap里面保存的session因为startCleanup函数执行停止在了2月4号,那么为什么startCleanup函数执行会停止在那一天呢,2个可能性:第一,startCleanup没有被调用了,第二,startCleanup内部执行到lastCleanup赋值之前的判断条件出错
先看startCleanup内部执行到lastCleanup赋值之前的判断条件, lastcleanup是否赋值取决于shouldRun变量和cleanupActive的compareAndSet函数返回结果,很显然,shouldRun是true,因为dirtyAge比cleanupDelayMils(10s)大得多,而cleanupActive的现在结果是0(从heapdump里面得出),所有compareAndSet的返回结果也是true,那么startCleanup给lastCleanup赋值的先决条件都是满足的,除非这里出现了硬件或者操作系统的错误:),那么接下来就把矛头对准了startCleanup的调用端,在下一篇里面再继续吧。
final long now = System.currentTimeMillis(); long dirtyAge = (now - lastCleanup); boolean shouldRun = forced || (store != null && dirty && dirtyAge >= writeDelayMillis) <strong>|| (dirtyAge > cleanupDelayMillis</strong>); if (shouldRun && cleanupActive.compareAndSet(false, true)) { lastCleanup = now;