2013-10-03 36 views
0

我的查詢以31,000個結果返回,每行有12列,每行包含大約8,000個字符(每行8KB)。下面是我如何處理:使用ResultSet從Oracle數據庫查詢時出現OutOfMemoryError

public List<MyTableObj> getRecords(Connection con) { 
    List<MyTableObj> list = new ArrayList<MyTableObj>(); 
    String sql = "my query..."; 

    ResultSet rs = null; 
    Statement st = null; 

    con.setAutoCommit(false); 
    st = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 
    st.setFetchSize(50); 
    rs = st.executeQuery(sql); 

    try { 
     System.out.println("Before MemoryFreeSize = " + (double)Runtime.getRuntime().freeMemory()/1024/1024 + " MB"); 

     while (rs.next()) { 
      MyTableObjitem item = new MyTableObj(); 
      item.setColumn1(rs.getString("column1")); 
      ... ... 
      item.setColumn12(rs.getString("column12")); 

      list.add(item); 
     } // end loop 

     // try to release some memory, but it's not working at all 
     if (st != null) st.close(); 
     if (rs != null) rs.close(); 
     st = null; rs = null; 
    } 
    catch (Exception e) { //do something } 
    System.out.println("After MemoryFreeSize = " + (double)Runtime.getRuntime().freeMemory()/1024/1024 + " MB"); 

    return list; 
} // end getRecords 

如果每行需要8kb內存,31k應占用242mb內存。在循環查詢結果之後,我剩餘的內存僅爲142mb,這不足以完成其他進程的其餘部分。

我搜索了很多解決方案,並試圖將我的堆內存設置爲512MB -Xmx512m -Xms512m,並且我還設置了獲取大小setFetchSize(50)

我懷疑是ResultSet佔用了太多的內存,結果可能存儲在客戶端catch。但是,在清理了一些對象(st.close()rs.close())後,即使我手動調用垃圾回收器System.gc(),循環後的空閒內存也不會增加(爲什麼?)。

我們假設我無法更改數據庫設計,並且我需要所有查詢結果。如何在處理後釋放更多內存?

P.S .:我也試過不使用ResultSet.getString()並將其與硬編碼字符串關聯,並且在循環之後,我獲得了450MB空閒內存。

我發現,如果我這樣做:

// + counter to make the value different for each row, for testing purpose 
item.setColumn1("Constant String from Column1" + counter); 
... ... 
item.setColumn12("Constant String from Column12" + counter); 
counter++; 

它曾經只有約60MB的內存。 但是,如果我這樣做:

item.setColumn1(rs.getString("column1")); 
... ... 
item.setColumn12(rs.getString("column12")); 

它使用高達380MB的內存。

我已經做過rs.close();rs = null;//rs is Result instance,但這似乎沒有幫助。這兩種方法之間爲什麼存在不同的內存使用情況?在這兩種方法中,我只通過String傳遞。

回答

0

我不認爲很多人可能會遇到這個問題,但我仍然想發佈我的解決方案以供參考。

在我的代碼之前, 查詢是:

String sql = "SELECT column1, column2 ... FROM mytable"; 

和MyTableObj二傳手是:

public void setColumn1(String columnStr) { 
    this._columnStr = columnStr == null ? "" : columnStr.trim(); 
} 

更新後:

我更新什麼是隻使用裝飾的查詢,而不是使用Java代碼:

String sql = "SELECT TRIM(column1), TRIM(column2) ... FROM mytable"; 

public void setColumn1(String columnStr) { 
    this._columnStr = columnStr == null ? "" : columnStr; 
} 

使用此udpated代碼,只需要大約100 MB內存,這比之前的內存使用情況少了很多(380 MB)。

我仍然不能給出一個有效的原因,爲什麼Java修剪會消耗更多的內存sql trim,如果有人知道原因,請幫我解釋一下。我會很感激。

經過多次測試,我發現它是數據。每行佔用8 KB,而31,000行佔用大約240MB內存。查詢中的TRIM只能用於這些短數據。

由於數據量大且內存有限,所以我現在只能限制查詢結果。

+0

其實你可能只是觀察它錯了。你認爲它使用了「太多」的內存,但它只是使用可用的內容,並沒有垃圾收集垃圾。你不會提到OutOfMemoryError,所以我假設你信任的是Runtime.freeMemory()值,只是**想着**你內存不足。 – Kayaman

+0

實際上大多數內存不被String.trim()消耗,這是我觀察錯誤的部分。它應該是數據本身。如果每個數據是8 KB,那麼30,000個數據必須使用高達240 MB的內存。 – qkzhu

+0

爲了得到正確的結果,應該使用profiler。不是Runtime.freeMemory()。 – Kayaman

0

您應該縮小您的疑問,請在查詢中得到更具體的,必要時附加限制你的java不能處理過大的結果

+0

問題是我不能限制我的查詢... – qkzhu

0

如果你需要的一切你在說內存中的數據同一時間(所以你不能分塊處理),那麼你需要有足夠的內存。用1G的內存嘗試一下。

忘記調用System.gc(),這不會有幫助(它會在OutOfMemoryException拋出之前調用)。

我也注意到你沒有關閉連接。你也應該這樣做(如果你還沒有連接池,請設置一個)。

當然,您可以使用探查器來查看內存實際正在到達的位置。你現在正在做的是純粹的猜測。

+0

使用'getRecords(Connection con)'函數後連接實際上是關閉的。 – qkzhu

相關問題