实战Java内存泄漏问题分析 -- hazelcast2.0.3使用时内存泄漏 -- 1


公司当年有一个自己缓存集群用户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), 如图

实战Java内存泄漏问题分析 -- hazelcast2.0.3使用时内存泄漏 -- 1_第1张图片

很明显OOM是因为HashEntry有进无出,为什么会有进无出呢?我们先看看hazlcast这方面的清除HashEntry的代码,代码中hazelcast的从CMap里面clear数据有2个方法 mapRecords:removeAndPurgeRecord 和purgeIfNotOwnedOrBackup. 它们只有一个调用端:startCleanup function. 如下图所示:

实战Java内存泄漏问题分析 -- hazelcast2.0.3使用时内存泄漏 -- 1_第2张图片

查看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)) {
            lastCleanup = now;
回到memory analyzer里面查看cmap的lastCleanup属性,诡异的发现时间停止在了1391476828844,用转换工具可知这个时间是: Tue, 04 Feb 2014 01:20:28 GMT.
实战Java内存泄漏问题分析 -- hazelcast2.0.3使用时内存泄漏 -- 1_第3张图片

现在我们知道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)
                || (dirtyAge > cleanupDelayMillis);
        if (shouldRun && cleanupActive.compareAndSet(false, true)) {
            lastCleanup = now;


你可能感兴趣的:(Java)