首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >java应用程序因可疑的jdbc内存泄漏而崩溃

java应用程序因可疑的jdbc内存泄漏而崩溃
EN

Stack Overflow用户
提问于 2016-07-24 05:47:08
回答 2查看 2.1K关注 0票数 0

我一直在开发一个java应用程序,它用http-client从Internet上爬行页面(Version4.3.3)。它使用一个fixedThreadPool和5个线程,每个线程都是一个循环线程,.The伪代码如下所示。

代码语言:javascript
复制
public class Spiderling extends Runnable{
  @Override
  public void run() {

    while (true) {
        T task = null;
        try {
            task = scheduler.poll();

            if (task != null) {
                if Ehcache contains task's config
                        taskConfig = Ehcache.getConfig;
                else{
                    taskConfig = Query task config from db;//close the conn every time
                    put taskConfig into Ehcache
                }


                spider(task,taskConfig);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    LOG.error("spiderling is DEAD");
}
}

我在服务器(2个cpus,2G内存)上使用以下参数-Duser.timezone=GMT+8 -server -Xms1536m -Xmx1536m -Xloggc:/home/datalord/logs/gc-2016-07-23-10-28-24.log -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC运行它,它大约每两到三天就会崩溃一次,没有OutOfMemoryError,也没有JVM错误日志。

这是我的分析;

  1. 我用GC-EASY分析gc日志,报告是这里的.奇怪的是,老世代增长缓慢,直到分配到最大堆大小,但完整的Gc从未发生过一次。
  2. 我怀疑它可能有内存泄漏,所以我使用cmd jmap -dump:format=b,file=soldier.bin转储堆映射,并使用Eclipse来分析转储file.Here是哪个对象占用280+ M字节的问题。

由“com.mysql.jdbc.NonRegisteringDriver@ 0xa0018490”加载的类"sun.misc.Launcher$AppClassLoader“占281,118,144字节(68.91%)。内存在由"“加载的"java.util.concurrent.ConcurrentHashMap$Segment[]”的一个实例中累积。 关键词java.util.concurrent.ConcurrentHashMap$Segment[] sun.misc.Launcher$AppClassLoader @ 0xa0018490。

我使用c3p0-0.9.1.2作为mysql连接池,mysql-连接器-java-5.1.34作为jdbc连接器,Ehcache-2.6.10作为内存缓存,我看到了所有关于'com.mysql.jdbc.NonregisteringDriver内存泄漏‘的帖子,但仍然没有任何线索。

这个问题已经把我逼疯了好几天了,任何建议或帮助都将不胜感激!

*07-24*

我使用一个名为jfinal的JAVA + ORM框架(github.com/jfinal/jfinal),它是在github中打开的。下面是一些关于这个问题的进一步描述的核心代码。

代码语言:javascript
复制
/**
 * CacheKit. Useful tool box for EhCache.
 * 
 */

public class CacheKit {

private static CacheManager cacheManager;
private static final Logger log = Logger.getLogger(CacheKit.class);

static void init(CacheManager cacheManager) {
    CacheKit.cacheManager = cacheManager;
}

public static CacheManager getCacheManager() {
    return cacheManager;
}

static Cache getOrAddCache(String cacheName) {
    Cache cache = cacheManager.getCache(cacheName);
    if (cache == null) {
        synchronized(cacheManager) {
            cache = cacheManager.getCache(cacheName);
            if (cache == null) {
                log.warn("Could not find cache config [" + cacheName + "], using default.");
                cacheManager.addCacheIfAbsent(cacheName);
                cache = cacheManager.getCache(cacheName);
                log.debug("Cache [" + cacheName + "] started.");
            }
        }
    }
    return cache;
}

public static void put(String cacheName, Object key, Object value) {
    getOrAddCache(cacheName).put(new Element(key, value));
}

@SuppressWarnings("unchecked")
public static <T> T get(String cacheName, Object key) {
    Element element = getOrAddCache(cacheName).get(key);
    return element != null ? (T)element.getObjectValue() : null;
}

@SuppressWarnings("rawtypes")
public static List getKeys(String cacheName) {
    return getOrAddCache(cacheName).getKeys();
}

public static void remove(String cacheName, Object key) {
    getOrAddCache(cacheName).remove(key);
}

public static void removeAll(String cacheName) {
    getOrAddCache(cacheName).removeAll();
}

@SuppressWarnings("unchecked")
public static <T> T get(String cacheName, Object key, IDataLoader dataLoader) {
    Object data = get(cacheName, key);
    if (data == null) {
        data = dataLoader.load();
        put(cacheName, key, data);
    }
    return (T)data;
}

@SuppressWarnings("unchecked")
public static <T> T get(String cacheName, Object key, Class<? extends IDataLoader> dataLoaderClass) {
    Object data = get(cacheName, key);
    if (data == null) {
        try {
            IDataLoader dataLoader = dataLoaderClass.newInstance();
            data = dataLoader.load();
            put(cacheName, key, data);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    return (T)data;
}

}

我使用CacheKit就像CacheKit.get("cfg_extract_rule_tree", extractRootId, new ExtractRuleTreeDataloader(extractRootId))一样。如果extractRootId没有在缓存中找到任何内容,则将调用类extractRootId

代码语言:javascript
复制
public class ExtractRuleTreeDataloader implements IDataLoader {
public static final Logger LOG = LoggerFactory.getLogger(ExtractRuleTreeDataloader.class);
private int                ruleTreeId;

public ExtractRuleTreeDataloader(int ruleTreeId) {
    super();
    this.ruleTreeId = ruleTreeId;
}

@Override
public Object load() {
    List<Record> ruleTreeList = Db.find("SELECT * FROM cfg_extract_fule WHERE root_id=?", ruleTreeId);
    TreeHelper<ExtractRuleNode> treeHelper = ExtractUtil.batchRecordConvertTree(ruleTreeList);//convert List<Record> to and tree
    if (treeHelper.isValidTree()) {
        return treeHelper.getRoot();
    } else {
        LOG.warn("rule tree id :{} is an error tree #end#", ruleTreeId);
        return null;
    }
}

如前所述,我使用JFinal ORM.The Db.find方法代码是

代码语言:javascript
复制
public List<Record> find(String sql, Object... paras) {
    Connection conn = null;
    try {
        conn = config.getConnection();
        return find(config, conn, sql, paras);
    } catch (Exception e) {
        throw new ActiveRecordException(e);
    } finally {
        config.close(conn);
    }
}

config close方法代码是

代码语言:javascript
复制
public final void close(Connection conn) {
    if (threadLocal.get() == null)      // in transaction if conn in threadlocal
        if (conn != null)
            try {conn.close();} catch (SQLException e) {throw new ActiveRecordException(e);}
}

我的代码中没有事务,所以我确信每次都会调用conn.close()。

*07-28*

首先,我使用Ehcache将taskConfigs存储在内存中。而且taskConfigs几乎从未改变过,所以我希望将它们永久存储在内存中,如果内存不能全部存储,则将它们存储到磁盘中。

我使用MAT来找出NonRegisteringDriver的GC根,结果显示在下面的图片中。NonRegisteringDriver的Gc根

但是,我仍然不明白为什么Ehcache引导内存leak.The taskConfig的默认行为是一个类扩展了模型类。

代码语言:javascript
复制
public class TaskConfig extends Model<TaskConfig> {
    private static final long    serialVersionUID = 5000070716569861947L;
    public static TaskConfig DAO              = new TaskConfig();

}

模型的源代码在这个page(github.com/jfinal/jfinal/blob/jfinal-2.0/src/com/jfinal/plugin/activerecord/Model.java).中我找不到任何引用(直接或间接)连接对象作为@Jeremiah猜测。

然后我阅读了NonRegisteringDriver的源代码,不明白为什么NonRegisteringDriver的map字段connectionPhantomRefs容纳了5000多个<ConnectionPhantomReference, ConnectionPhantomReference>入口,但是在NonRegisteringDriver的队列字段refQueue中没有找到ConnectionImpl。因为我在AbandonedConnectionCleanupThread类中看到了清理代码,这意味着它将在NonRegisteringDriver.connectionPhantomRefs中移动ref,同时从NonRegisteringDriver.refQueue获得放弃的连接ref

代码语言:javascript
复制
@Override
public void run() {
    threadRef = this;
    while (running) {
        try {
            Reference<? extends ConnectionImpl> ref = NonRegisteringDriver.refQueue.remove(100);
            if (ref != null) {
                try {
                    ((ConnectionPhantomReference) ref).cleanup();
                } finally {
                    NonRegisteringDriver.connectionPhantomRefs.remove(ref);
                }
            }

        } catch (Exception ex) {
            // no where to really log this if we're static
        }
    }
}

感谢@Jeremiah提供的帮助!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-08-10 10:14:24

java程序的根本原因是Linux操作系统耗尽了内存,而OOM扼杀了进程。我发现了/var/ log /消息中的日志,如下所示。

代码语言:javascript
复制
Aug  3 07:24:03 iZ233tupyzzZ kernel: Out of memory: Kill process 17308 (java) score 890 or sacrifice child
Aug  3 07:24:03 iZ233tupyzzZ kernel: Killed process 17308, UID 0, (java) total-vm:2925160kB, anon-rss:1764648kB, file-rss:248kB
Aug  3 07:24:03 iZ233tupyzzZ kernel: Thread (pooled) invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
Aug  3 07:24:03 iZ233tupyzzZ kernel: Thread (pooled) cpuset=/ mems_allowed=0
Aug  3 07:24:03 iZ233tupyzzZ kernel: Pid: 6721, comm: Thread (pooled) Not tainted 2.6.32-431.23.3.el6.x86_64 #1

我还发现maxIdleTime的默认值是C3p0Plugin中的20秒,这是JFinal中的c3p0插件,所以我认为这就是为什么NonRegisteringDriver对象占用MAT报告中显示的280+ M字节的原因。因此,我将maxIdleTime设置为3600秒,NonRegisteringDriver对象在MAT报告中不再可疑。

我将jvm参数重置为-Xms512m -Xmx512m。java程序已经运行了好几天了。当老世代满时,会像预期的那样调用完整的Gc。

票数 0
EN

Stack Overflow用户

发布于 2016-07-25 01:31:10

从上面的注释中,我几乎可以确定您的内存泄漏实际上是来自EhCache的内存使用。您看到的ConcurrentHashMap是支持MemoryStore的那个,我猜taskConfig保存了一个对连接对象的引用(直接或间接),这就是为什么它显示在堆栈中。

在默认缓存中使用eternal="true“使其不允许插入的对象过期。即使没有这种情况,timeToLive和timeToIdle值默认为无限生命周期!

将其与Ehcache的默认行为结合起来,在检索元素时通过序列化复制它们(上次我检查了它们)!每次提取taskConfig并将其放回ehcache时,您只需将新的对象引用堆叠起来。

最好的测试方法(在我看来)是更改默认的缓存配置。将永久更改为false,并实现timeToIdle值。timeToIdle是指一个值可能存在于缓存中而不被访问的时间(以秒为单位)。

代码语言:javascript
复制
 <ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdle="120"  overflowToDisk="true" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"/>

如果这样做有效,那么您可能希望进一步调整ehcache配置设置,或者为类提供一个除默认之外的更自定义的缓存引用。

在调整ehcache时有多个性能考虑。我确信您的业务模式有更好的配置。Ehcache文档很好,但是当我试图找出它的时候,我发现这个站点有点分散。下面我列出了一些有用的链接。

http://www.ehcache.org/documentation/2.8/configuration/cache-size.html

http://www.ehcache.org/documentation/2.8/configuration/configuration.html

http://www.ehcache.org/documentation/2.8/apis/cache-eviction-algorithms.html#provided-memorystore-eviction-algorithms

祝好运!

要测试内存泄漏,请尝试以下操作:

  1. 将TaskConfig插入ehcache中
  2. 立即从缓存中取回它。
  3. 输出TaskConfig1.Equals(TaskConfig2)的值;

如果它返回false,那就是内存泄漏。重写equalshash对象中的TaskConfig对象,并重新运行测试。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38549126

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档