2017-05-26 7 views
0

私はSybaseデータベースからデータを取り込み、UCanAccessを使用してMicrosoft AccessデータベースにインポートするJavaプログラムを作成しています。しかし、私は現在、 "java.lang.OutOfMemoryError:GCオーバーヘッド限界を超えました"というエラーを受け取って、問題に直面しています。ヒープサイズを増やさずにGCオーバーヘッド限界を超えて固定

状況に合わせて、私は約130万レコードをAccessデータベースにインポートしようとしています。これらのレコードの約800,000をインポートした後、実行時に約10分、ResultSetをSybaseデータベースから取得した後に、プログラムは現在、エラーに遭遇しています。

ヒープサイズを変更しようとしましたが、プログラムの速度が大幅に低下します。これは必要に応じて複数回実行される特別なプログラムなので、実行時間は数分または数時間のオーダーでなければならないが、観測に基づいてヒープサイズを大きくすると実行時間がオーダーに増加する日の

参照のために、getRecordsというサブルーチンの間にmainメソッドでエラーが発生します(このエラーが発生するコードの行は、実行ごとに異なります)。私は、以下のプログラムにコードを追加しました。コードの一部にマイナーな変更を加えました。私が使用している正確なクエリやアクセスデータベースへのユーザー名とパスワードなど、機密情報を漏らさないようにしています。

私のプログラムのコードで、実行時間を数時間以上増やすことなくガベージコレクタの負荷を軽減するために変更できることはありますか?

編集:私はJavaのデフォルトの最大ヒープサイズと誤っているようです。ヒープサイズを512mに設定してヒープサイズを増やしていると思ったとき、ヒープサイズを意図せずに半分にカットしていました。代わりにヒープサイズを2048mに設定すると、Javaヒープスペースエラーが発生します。可能であれば、ヒープサイズを変更せずに問題を解決したいと思います。

編集2:明らかに、私は処理する必要があるいくつかのレコードについて誤解を受けていました。私が当初考えていたサイズの2倍であり、私のアプローチを大きく変える必要があることを示しています。その答えは大きな改善をもたらしたので、先に進んで答えを受け入れようとしています。

getRecords方法:

public static void getRecords(SybaseDatabase sdb, AccessDatabase adb) 
    { 
     ArrayList<Record> records = new ArrayList<Record>(); 
     StringBuffer sql = new StringBuffer(); 
     Record currentRecord = null; 
     try{ 
      Statement sybStat = sdb.connection.createStatement(); 
      PreparedStatement resetADB = adb.connection.prepareStatement("DELETE FROM Table"); 
      PreparedStatement accStat = adb.connection.prepareStatement("INSERT INTO Table (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); 
sql.append(query);//query is a placeholder, as I cannot give out the actual query to the database. I have confirmed that the query itself gives the ResultSet that I am looking for 
      ResultSet rs = sybStat.executeQuery(sql.toString()); 
      resetADB.executeUpdate(); 
      boolean nextWatch = true; 
      Integer i = 1; 
      Record r = new Record(); 
      while(nextWatch) 
      { 
       for (int j = 0; j < 1000 && nextWatch; j++) 
       { 
        nextWatch = rs.next(); 

        r.setColumn(i, 0); 
        r.setColumn(rs.getString("B"), 1); 
        r.setColumn(rs.getString("C"), 2); 
        r.setColumn(rs.getString("D"), 3); 
        r.setColumn(rs.getString("E"), 4); 
        r.setColumn(rs.getString("F"), 5); 
        r.setColumn(rs.getString("G"), 6); 
        r.setColumn(rs.getString("H"), 7); 
        r.setColumn(rs.getString("I"), 8); 
        r.setColumn(rs.getString("J"), 9); 
        r.setColumn(rs.getString("K"), 10); 
        r.setColumn(rs.getInt("L"), 11); 
        r.setColumn(rs.getString("M"), 12); 
        r.setColumn(rs.getString("N"), 13); 
        r.setColumn(rs.getString("O"), 14); 
        r.setColumn(rs.getString("P"), 15); 

        records.add(r); 
        i++; 
       } 

       for(int k = 0; k < records.size(); k++) 
       { 
        currentRecord = records.get(k); 

        for(int m = 0; m < currentRecord.getNumOfColumns(); m++) 
        { 
         if (currentRecord.getColumn(m) instanceof String) 
         { 
          accStat.setString(m + 1, "\"" + currentRecord.getColumn(m) + "\""); 
         } 
         else 
         { 
          accStat.setInt(m + 1, Integer.parseInt(currentRecord.getColumn(m).toString())); 
         } 
        } 
        accStat.addBatch(); 
       } 
       accStat.executeBatch(); 
       accStat.clearBatch(); 
       records.clear(); 
      } 
      adb.connection.commit(); 
     } 
     catch(Exception e){ 
      e.printStackTrace(); 
     } 
     finally{ 

     } 
    } 
} 

全コード:

import java.util.*; 
import java.sql.*; 
import com.sybase.jdbc2.jdbc.SybDriver;//This is an external file that is used to connect to the Sybase database. I will not include the full code here for the sake of space but will provide it upon request. 

public class SybaseToAccess { 
    public static void main(String[] args){ 
     String accessDBPath = "C:/Users/me/Desktop/Database21.accdb";//This is a placeholder, as I cannot give out the exact file path. However, I have confirmed that it points to the correct file on the system. 
     String sybaseDBPath = "{sybServerName}:{sybServerPort}/{sybDatabase}";//See above comment 
     try{ 
      AccessDatabase adb = new AccessDatabase(accessDBPath); 
      SybaseDatabase sdb = new SybaseDatabase(sybaseDBPath, "user", "password"); 

      getRecords(sdb, adb); 
     } 
     catch(Exception e){ 
      e.printStackTrace(); 
     } 
     finally{ 

     }  
    } 
    public static void getRecords(SybaseDatabase sdb, AccessDatabase adb) 
    { 
     ArrayList<Record> records = new ArrayList<Record>(); 
     StringBuffer sql = new StringBuffer(); 
     Record currentRecord = null; 
     try{ 
      Statement sybStat = sdb.connection.createStatement(); 
      PreparedStatement resetADB = adb.connection.prepareStatement("DELETE FROM Table"); 
      PreparedStatement accStat = adb.connection.prepareStatement("INSERT INTO Table (A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); 
sql.append(query);//query is a placeholder, as I cannot give out the actual query to the database. I have confirmed that the query itself gives the ResultSet that I am looking for 
      ResultSet rs = sybStat.executeQuery(sql.toString()); 
      resetADB.executeUpdate(); 
      boolean nextWatch = true; 
      Integer i = 1; 
      Record r = new Record(); 
      while(nextWatch) 
      { 
       for (int j = 0; j < 1000 && nextWatch; j++) 
       { 
        nextWatch = rs.next(); 

        r.setColumn(i, 0); 
        r.setColumn(rs.getString("B"), 1); 
        r.setColumn(rs.getString("C"), 2); 
        r.setColumn(rs.getString("D"), 3); 
        r.setColumn(rs.getString("E"), 4); 
        r.setColumn(rs.getString("F"), 5); 
        r.setColumn(rs.getString("G"), 6); 
        r.setColumn(rs.getString("H"), 7); 
        r.setColumn(rs.getString("I"), 8); 
        r.setColumn(rs.getString("J"), 9); 
        r.setColumn(rs.getString("K"), 10); 
        r.setColumn(rs.getInt("L"), 11); 
        r.setColumn(rs.getString("M"), 12); 
        r.setColumn(rs.getString("N"), 13); 
        r.setColumn(rs.getString("O"), 14); 
        r.setColumn(rs.getString("P"), 15); 

        records.add(r); 
        i++; 
       } 

       for(int k = 0; k < records.size(); k++) 
       { 
        currentRecord = records.get(k); 

        for(int m = 0; m < currentRecord.getNumOfColumns(); m++) 
        { 
         if (currentRecord.getColumn(m) instanceof String) 
         { 
          accStat.setString(m + 1, "\"" + currentRecord.getColumn(m) + "\""); 
         } 
         else 
         { 
          accStat.setInt(m + 1, Integer.parseInt(currentRecord.getColumn(m).toString())); 
         } 
        } 
        accStat.addBatch(); 
       } 
       accStat.executeBatch(); 
       accStat.clearBatch(); 
       records.clear(); 
      } 
      adb.connection.commit(); 
     } 
     catch(Exception e){ 
      e.printStackTrace(); 
     } 
     finally{ 

     } 
    } 
} 

class AccessDatabase{ 
    public Connection connection = null; 
    public AccessDatabase(String filePath) 
      throws Exception 
     { 
      String dbString = null; 
      dbString = "jdbc:ucanaccess://" + filePath; 
      connection = DriverManager.getConnection(dbString); 
      connection.setAutoCommit(false); 
     } 
} 
class Record{ 
    ArrayList<Object> columns; 
public 
    Record(){ 
     columns = new ArrayList<Object>(); 
     columns.add("Placeholder1"); 
     columns.add("Placeholder2"); 
     columns.add("Placeholder3"); 
     columns.add("Placeholder4"); 
     columns.add("Placeholder5"); 
     columns.add("Placeholder6"); 
     columns.add("Placeholder7"); 
     columns.add("Placeholder8"); 
     columns.add("Placeholder9"); 
     columns.add("Placeholder10"); 
     columns.add("Placeholder11"); 
     columns.add("Placeholder12"); 
     columns.add("Placeholder13"); 
     columns.add("Placeholder14"); 
     columns.add("Placeholder15"); 
     columns.add("Placeholder16"); 
    } 

    <T> void setColumn(T input, int colNum){ 
     columns.set(colNum, input); 
    } 

    Object getColumn(int colNum){ 
     return columns.get(colNum); 
    } 

    int getNumOfColumns() 
    { 
     return columns.size(); 
    } 
} 

class SybaseDatabase{ 
    public Connection connection; 

    @SuppressWarnings("deprecation") 
    public SybaseDatabase(String filePath, String Username, String Password) 
     throws Exception 
    { 
     SybDriver driver; 

     try 
     { 
      driver = (SybDriver)Class.forName("com.sybase.jdbc2.jdbc.SybDriver").newInstance(); 
      driver.setVersion(SybDriver.VERSION_6); 
      DriverManager.registerDriver(driver); 
     } 
     catch (Exception e) 
     { 
      e.printStackTrace(System.err); 
     } 

     connection = DriverManager.getConnection("jdbc:sybase:Tds:" + filePath, Username, Password); 
    } 
} 
+0

ベストプラクティスは、同じ時間に1つのトランザクションだけで1.3百万行のデータを処理できないようです。あなたは "数"行(10000または100,000行のパケットで扱うかもしれない)でデータを分割することで、これらのレコードを処理することはできませんか?そのように、あまりデータを記憶しないでください。 – Prim

+0

@Primコミットについて話していると、私は最近、ちょうど1つの大きなトランザクションの代わりに1000行ごとにコミットするようにプログラムを修正しようとしました。アクセスDBにインポートされた約950000行の改善がありましたが、それでもそれ以降は爆発しました。 – Tyrovar

+0

あなたの主な問題は、まだArrayList にはまだ1.3百万の行がロードされており、1000行だけを読み込んで読み込み(多分行数を制限するためにSQLリクエストを変更することによって)、これらの行を処理してコミットします。そう(すべてのレコードを参照せずに)。 – Prim

答えて

0

あなたは、あなたが同じ時間でより少ない行を処理しなければならない、より少ないメモリを使用しますが、同様に(あなたが再利用できるすべてのオブジェクトを再利用する場合PreparedStatement

最初に、固定サイズのレコードでArrayList<>を使用します。そのために配列Record[]を使うことができます。 ArrayListの原則は、ここでは必要のない動的なサイズの配列を持つことです。

2番目:処理する前にすべてのデータをデータベースから読み込み、データの一部を読み込んで処理し、持続する。

コードの一部を抽出していくつかの行を処理し、クエリをlimiting the number of returned rowsで変更することで、これを行うことができます。

ここでは、1000行(インデックス0から999まで)をロードして処理してコミットします。その後、1000行(インデックス1000から1999まで)をロードし、処理してコミットします。そして、あなたは続けます。行の各パックの間で、(レコードなどの)歳差しされたデータを参照しないでください(必要に応じてガベージコレクションされるように)。

メモリがまだ不足している場合は、そのためにガベージコレクションされていないオブジェクトがメモリリークの原因となっていると思われます。各データを処理するにはメモリが必要です。

関連する問題