2016-12-08 13 views
0

私はこれまでに何度も尋ねられてきた同様の質問をしていますが、解決策の多くを試したとしても、私はまだこの問題を抱えています。Java PreparedStatement java.lang.OutOfMemoryError:GCオーバーヘッドの上限を超えました

私たちのアプリケーションでは、パラメータ化された生のSQLクエリーを作成して、エクセルのスプレッドシートにダウンロードされたDBからデータを抽出することができます。

小さいデータセットの場合、これはうまくいきますが、ファイルサイズが10MBに近づくと、この問題が発生します。

データセットは、100k行または80-90mbの可能性があります。可能であれば、JVMヒープサイズを増やしたくありません。

私のコードには目に見えない目障りなエラーがあります。 resultSet.next()ループが問題の原因になっているようです。ヒープスペースを不安定にするのを防ぐためにこれを書くより効率的な方法はありますか?

ご迷惑をおかけして申し訳ございません。あなたは全体のResultSetをロードしているため、クエリの結果は大量のデータを返す場合のおかげ

/* 
* 
* query is a raw sql query that takes parameters (using Mybatis) 
* criteriaMap the arguments that we subsitute into the query 
* 
*/ 

public List<Map<String, Object>> queryForJsonWithoutMapping(final String query, final Map<String, Object> criteriaMap){ 

SqlSession sqlSession = getSqlSessionInstance(); 

    String sql = ""; 
    Connection connection = null; 
    PreparedStatement pstmt = null; 
    ResultSet resultSet = null; 

    try { 

     final Configuration configuration = getSqlSessionInstance().getConfiguration(); 

     SqlSourceBuilder builder = new SqlSourceBuilder(configuration); 

     SqlSource src = builder.parse(query, Map.class, null); 

     BoundSql boundSql = src.getBoundSql(criteriaMap); 

     sql = boundSql.getSql(); 

     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 

     connection = sqlSession.getConnection(); 

     pstmt = connection.prepareStatement(sql, java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); 

     // this function subs the params into the preparedStatement query 
     buildParams(parameterMappings, criteriaMap, pstmt); 

     resultSet = pstmt.executeQuery(); 

     // the while loop inside this function is where things start to hang 
     List<Map<String, Object>> results = getObjectFromResultSet(resultSet); 

     return results; 

    } catch (Exception e) { 
     LOG.error(e.getMessage(), e); 
     LOG.error(ExceptionUtils.getStackTrace(e)); 
     throw new IllegalStateException(sql + " " + e.getMessage(), e); 
    } finally { 
     try{ 
      connection.close(); 
      pstmt.close(); 
      resultSet.close(); 
     }catch (SQLException e){ 
      e.printStackTrace(); 
     } 
     sqlSession.close(); 
    } 

private List<Map<String, ?>> getEntitiesFromResultSet(ResultSet resultSet) throws SQLException { 
     ArrayList<Map<String, ?>> entities = new ArrayList<>(resultSet.getFetchSize()); 
     int index = 0; 
     Map<String, Object> jsonObject; 
     while (resultSet.next()) { 
      jsonObject = getEntityFromResultSet(resultSet); 
      entities.add(index, jsonObject); 
      index ++; 
     } 
     resultSet.close(); 
     return entities; 
    } 

    private List<Map<String, Object>> getObjectFromResultSet(ResultSet resultSet) throws SQLException { 
     ArrayList<Map<String, Object>> entities = new ArrayList<>(resultSet.getFetchSize()); 
     int index = 0; 
     Map<String, Object> jsonObject; 
     while (resultSet.next()) { 
      jsonObject = getEntityFromResultSet(resultSet); 
      entities.add(index, jsonObject); 
      index ++; 
     } 
     resultSet.close(); 
     return entities; 
    } 

DBは、いくつかの点でメモリのうち、このようなデザイン、あなたは意志必然的な実行でOracle

+3

伝統的な「メモリにすべてを読み込んでから、何かをやり始めます」のように見えます。あなたは読んでいるときに結果を流すことができます、それはずっと効率的です。 – Kayaman

+0

entities.add(index、jsonObject);これが原因です。高速でそのメモリを利用する – Olu

答えて

0

ですメモリ。代わりに、あなたはgetXXXFromResultSet APIがデータ量の点でしきい値を持っていることだけを述べることができます。すべての行について、サイズを計算し、JSONドキュメントに追加できるかどうかを判断します。しきい値を超えた場合は、そこで停止してResultSetを閉じます(サーバーでの実行がキャンセルされます)。もう一つの選択肢は結果をストリーミングすることですが、それはより複雑です。

0

DBテーブルからすべての行を取得して処理するのは悪い考えです。 ページネーションを実装する必要があります。つまり、一度に1ページ(n =ページサイズ行)を読み込んで処理します。

あなたのページサイズは、あまりに多くのDBヒットをしないで、同時にメモリにあまり多くのレコードを持たないように、最適なサイズにする必要があります。

JdbcPagingItemReaderのSpring Batch APIがこのコンセプトを実装しています。

このSO Questionを参照すると、JDBCを使用したページネーションのアイデアがもっとたくさん得られます。

さらに、マップresultsのサイズを大きくしないでください。あなたはサイクルでこのマップをフラッシュする必要があります。

これが役立ちますように!

関連する問題