天天看点

记一次内存溢出的问题

问题

再设置java内存堆栈为512m的时候.定时任务一天就会崩溃,各种链接断开  (第一反应,以为服务器断网了)

第二次将java内存堆栈设置为4g.定时任务间隔了7天成功再次崩溃,链接断开,并且.捕捉到java堆栈溢出的错误

重启后.开始进行每天1次的内存堆栈跟踪

具体命令如下

查看当前已启动的进程和对应的PID

jps

将当前内存映射出1个文件

jmap -dump:format=b,file=heap.bin <pid> 

从docker文件种拷贝出内存分析文件

docker cp <id>:/heap.bin

通过jumpserver将此文件拉取到本地

Java自带内存分析程序

jhat -J-Xmx512m heap.bin

很遗憾 3.3g的内存文件显而易见的内存溢出不能分析, 请原谅公司给我配的电脑只有8g内存

于是使用JProfiler 进行内存分析

记一次内存溢出的问题

这边可以观察到占用内存最大的是一个char[]类型的数组

记一次内存溢出的问题

进入内部分析,观察到对象是由druid持有的

于是进入druid查看源码

private final void internalAfterStatementExecute(StatementProxy statement, boolean firstResult,
                                                 int... updateCountArray) {
    final long nowNano = System.nanoTime();
    final long nanos = nowNano - statement.getLastExecuteStartNano();

    JdbcDataSourceStat dataSourceStat = statement.getConnectionProxy().getDirectDataSource().getDataSourceStat();
    dataSourceStat.getStatementStat().afterExecute(nanos);

    final JdbcSqlStat sqlStat = statement.getSqlStat();

    if (sqlStat != null) {
        sqlStat.incrementExecuteSuccessCount();

        sqlStat.decrementRunningCount();
        sqlStat.addExecuteTime(statement.getLastExecuteType(), firstResult, nanos);
        statement.setLastExecuteTimeNano(nanos);
        if ((!firstResult) && statement.getLastExecuteType() == StatementExecuteType.Execute) {
            try {
                int updateCount = statement.getUpdateCount();
                sqlStat.addUpdateCount(updateCount);
            } catch (SQLException e) {
                LOG.error("getUpdateCount error", e);
            }
        } else {
            for (int updateCount : updateCountArray) {
                sqlStat.addUpdateCount(updateCount);
                sqlStat.addFetchRowCount(0);
                StatFilterContext.getInstance().addUpdateCount(updateCount);
            }
        }

        long millis = nanos / (1000 * 1000);
        if (millis >= slowSqlMillis) {   // slowSqlMillis= 3000
            // 解析查询条件
            String slowParameters = buildSlowParameters(statement);
            // 大对象LastSlowParameter的由来
            sqlStat.setLastSlowParameters(slowParameters);

            String lastExecSql = statement.getLastExecuteSql();
            if (logSlowSql) {
                LOG.error("slow sql " + millis + " millis. " + lastExecSql + "" + slowParameters);
            }

            handleSlowSql(statement);
        }
    }

    String sql = statement.getLastExecuteSql();
    StatFilterContext.getInstance().executeAfter(sql, nanos, null);

    Profiler.release(nanos);
}
           
public JdbcSqlStat createSqlStat(String sql) {
    lock.writeLock().lock();
    try {
        JdbcSqlStat sqlStat = sqlStatMap.get(sql);
        if (sqlStat == null) {
            sqlStat = new JdbcSqlStat(sql);
            sqlStat.setDbType(this.dbType);
            sqlStat.setName(this.name);
            sqlStatMap.put(sql, sqlStat);
        }

        return sqlStat;
    } finally {
        lock.writeLock().unlock();
    }
}
           

 可以看到.他是通过sql直接进行映射的.所以.当使用insert into  values 的时候.由于每次定时任务插入的数据都不同,导致sqlStatMap存入的sql语句越来越多.最好导致内存溢出,将大部分的链接都断掉

修改方法

1.使用insert into  values 的控制每次values的长度,控制到一致

2.修改durid的源码,使其抛出insert的语法

3.更换HikariCP数据源