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

时间:2016-07-24 05:47:09

标签: jdbc memory-leaks

我一直致力于使用http-client(版本4.3.3)从Internet抓取页面的java应用程序。它使用一个带有5个线程的fixedThreadPool,每个线程都是一个循环线程。伪代码正在跟随。

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日志,报告为here。奇怪的是Old Gen慢慢增加,直到分配的最大堆大小,但Full Gc从未发生过一次。
  2. 我怀疑它可能有内存泄漏,所以我使用cmd jmap -dump:format=b,file=soldier.bin转储堆映射并使用Eclipse MAT分析转储文件。这是一个问题,怀疑哪个对象占用280多个M字节。
  3.   

    班级" com.mysql.jdbc.NonRegisteringDriver",   由" sun.misc.Launcher $ AppClassLoader @ 0xa0018490"加载,占用281,118,144   (68.91%)个字节。内存在一个实例中累积   " java.util.concurrent.ConcurrentHashMap中$段[]"由""。

    加载      

    关键字   com.mysql.jdbc.NonRegisteringDriver   java.util.concurrent.ConcurrentHashMap中的$段[]   sun.misc.Launcher $ AppClassLoader @ 0xa0018490。

    我使用c3p0-0.9.1.2作为mysql连接池,使用mysql-connector-java-5.1.34作为jdbc接口,使用Ehcache-2.6.10作为内存cache.I已经看到所有关于&com; mysql的帖子.jdbc.NonregisteringDriver内存泄漏'仍然没有任何线索。

    这个问题让我疯了几天,任何建议或帮助都会受到赞赏!

    ********************** 07-24的补充说明****************

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

    /**
     * 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.get("cfg_extract_rule_tree", extractRootId, new ExtractRuleTreeDataloader(extractRootId))这样的CacheKit。如果ExtractRuleTreeDataloader在缓存中找不到任何内容,则会调用extractRootId类。

    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。Db.find方法代码是

    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方法代码为

    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根,结果显示在下图中。 The Gc Roots of NonRegisteringDriver

    但是我仍然不明白为什么Ehcache的默认行为导致内存泄漏.taskConfig是一个扩展Model类的类。

    public class TaskConfig extends Model<TaskConfig> {
        private static final long    serialVersionUID = 5000070716569861947L;
        public static TaskConfig DAO              = new TaskConfig();
    
    }
    

    和Model的源代码在此页面中(github.com/jfinal/jfinal/blob/jfinal-2.0/src/com/jfinal/plugin/activerecord/Model.java)。并且我无法像@Jeremiah猜测那样(直接或间接)找到连接对象的任何引用。

    然后我阅读了NonRegisteringDriver的源代码,并且不明白为什么connectionPhantomRefs的地图字段NonRegisteringDriver拥有超过5000个<ConnectionPhantomReference, ConnectionPhantomReference>,但找不到{ {1}}的队列字段ConnectionImpl中的{1}}。因为我在类refQueue中看到了清理代码,这意味着它会在NonRegisteringDriver中移动AbandonedConnectionCleanupThread,同时从ref放弃连接NonRegisteringDriver.connectionPhantomRefs

    ref

    感谢@Jeremiah提供的帮助!

2 个答案:

答案 0 :(得分:1)

从上面的评论中我几乎可以肯定你的内存泄漏实际上来自EhCache的内存使用情况。您正在查看的ConcurrentHashMap支持MemoryStore,我猜测taskConfig持有(直接或间接)连接对象的引用,这就是它在您的显示中显示的原因叠加。

永恒=&#34;真&#34;在默认缓存中使得插入对象永远不会过期。即使没有它,timeToLive和timeToIdle值也默认为无限生命周期!

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

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

 <ehcache> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdle="120"  overflowToDisk="true" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"/>

如果可行,那么您可能需要进一步调整ehcache配置设置,或者为您的班级提供非默认的更多自定义缓存参考。

调整ehcache时有多种性能考虑因素。我确信您的商业模式有更好的配置。 Ehcache文档很好,但是当我试图找出它时,我发现该网站有点分散。我列出了一些我认为有用的链接。

Live demo

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

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

祝你好运!

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

  1. 将TaskConfig插入ehcache
  2. 立即从缓存中检索回来。
  3. 输出TaskConfig1.equals(TaskConfig2);
  4. 的值
      

    如果返回false,那就是你的内存泄漏。覆盖equals和   在TaskConfig对象中hash并重新运行测试。

答案 1 :(得分:0)

java程序的根本原因是Linux操作系统内存不足,而OOM Killer杀死了进程。 我在/ var / log / messages中找到了如下记录。

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占用280+ M的原因MAT报告中显示的字节数。所以我将maxIdleTime设置为3600秒,对象NonRegisteringDriver在MAT报告中不再可疑。

我将jvm argements重置为-Xms512m -Xmx512m。 java程序已经运行了好几天了。当Old Gen已满时,将按预期调用Full Gc。