使用ResultSet从Oracle DB查询时的OutOfMemoryError

时间:2013-10-03 07:29:46

标签: java oracle memory resultset

我的查询返回31,000个结果,每行12列,每行包含大约8,000个字符(每行8KB)。以下是我的处理方式:

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占用了太多的记忆,结果可能存储在客户端捕获。但是,在我清理了一些对象(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。

3 个答案:

答案 0 :(得分:0)

您应该缩小查询范围,尝试更具体,如果有必要,在查询中添加限制,您的java无法处理太大的结果

答案 1 :(得分:0)

如果您需要同时在内存中获取的所有数据(因此无法以块的形式处理它),那么您将需要有足够的内存。尝试使用1G内存。

忘记调用System.gc(),这不会有帮助(无论如何都会在抛出OutOfMemoryException之前调用它。)

我也注意到你没有关闭连接。您也应该这样做(如果您还没有连接池,请设置一个)。

当然,您可以使用分析器查看内存的实际位置。你现在正在做的是纯粹的猜测。

答案 2 :(得分:0)

我不认为很多人可能会遇到这个问题,但我仍然想发布我的解决方案以供参考。

在我的代码之前 查询是:

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

和MyTableObj的setter是:

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

更新后:

我更新的内容只是在查询中使用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 trim会消耗更多内存sql trim,如果有人知道原因,请帮我解释一下。我会非常感激。

经过多次测试,我发现它是数据。每行占用8 KB,31,000行占用大约240 MB内存。查询中的TRIM只能用于那些短数据。

由于数据很大且内存有限,我现在只能限制查询结果。