天天看點

記一次記憶體溢出的問題

問題

再設定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資料源