如何在通过jdbc获取数据后释放内存

3
我正在使用Spring JDBC在WebLogic上进行编程。我将获取数据的fetch size设置为500,以便更快地从数据库中获取数据。但这会导致内存问题。以下是一个示例:

http://webmoli.com/2009/02/01/jdbc-performance-tuning-with-optimal-fetch-size/

我的问题是如何释放这段内存?运行GC无效,我猜测这是因为连接池中的连接仍然有效。
代码:
public List<Msisdn> getNewMsisdnsForBulkSmsId(String bulkSmsId,String scheduleId,final int msisdnCount) throws SQLException {
            JdbcTemplate jdbcTemplate = getJdbcTemplate();
            jdbcTemplate.setFetchSize(500);
            jdbcTemplate.setMaxRows(msisdnCount);
            jdbcTemplate.query("select BULKSMS_ID, ? as , STATUSSELECTDATE, DELIVERYTIME, ID, MESSAGE from ada_msisdn partition (ID_"+bulkSmsId+") where bulksms_id = ? and status = 0 and ERRORCODE = 0 and  SCHEDULEID is null for update skip locked", new Object[]{scheduleId,bulkSmsId}, MsisdnRowMapper.INSTANCE);

  //Also i tried to close connection and run gc, this does not free the memory too.
            //jdbcTemplate.getDataSource().getConnection().close();
            //System.gc();

            return null;
}

当我将获取大小设置为10时,堆大小为12 MB。如果将获取大小设置为500,则堆大小为206 MB。
谢谢。

1
如果您使用分析工具,您应该能够看到为什么会保留这些内存。 - Peter Lawrey
似乎连接正在分配内存,在我的代码中没有内存泄漏:60.9% - 206 MB - 561,359 alloc。org.springframework.jdbc.core.JdbcTemplate.query - Levent Tokmak
如果您将获取大小设置为10,问题是否真的消失了? - jbindel
是的,但目前查询时间太长了。每次获取500条数据会更快一些。 - Levent Tokmak
1个回答

1

关于添加示例代码等更新:

看起来你只需要使用小于500的值,但这让我想到你返回了比结果集映射器实际使用的数据要多得多的数据。

现在我看到你将所有映射结果存储在一个List中,我会说,与获取大小相关的问题可能是次要问题。 List<Msisdn>和单个获取的ResultSet行所需的组合内存空间已经超过了可用内存。

msisdnCount的值是多少? 如果它大于500,则您在list中使用的内存可能比ResultSet的500条记录还要多。 如果它小于500,则我预计当您将获取大小设置为msisdnCount时,内存问题也会发生,并且错误将在min(msisdnCount, 500)和10之间的某个值消失。

将所有结果加载到列表中,然后处理它们是一种很容易导致内存耗尽的模式。常见的解决方案是使用。如果您可以在每行进来时处理它,并且将所有映射结果存储在您的list中,则可以避免内存问题。

我没有在Spring JDBC核心包中看到任何流支持,但如果我找到了,我会更新的。

--

如果你检索的行中的数据足够大,以至于获取500行将耗尽你的堆栈内存,那么你必须要么减少每行返回的数据量,要么每次获取更少的行。

你可能会发现,在代码中存储了已获取的行,这意味着不是 ResultSet 占用了你的内存。例如,你可能会将所有行复制到某个集合实例中。

我建议先查看每行数据的大小,并尝试减少可能包含大型数据类型的不必要列,然后尝试简单地加载数据并迭代处理结果,而不执行正常的处理过程,这可能会将数据存储在某处,以查看你可以使用你拥有的内存一次加载多少行数据。如果你在获取500行时耗尽了内存,那么你一定拉取了很多数据。如果你实际上没有使用这些数据,那么你浪费了 CPU 和网络资源以及内存。

编辑:你可能还想设置光标的行为,以便让JDBC驱动程序更好地知道它可以丢弃什么。例如,您可以使用ResultSet.TYPE_FORWARD_ONLY、ResultSet.CONCUR_READ_ONLY准备语句。 http://docs.oracle.com/javase/6/docs/api/index.html?java/sql/ResultSet.html


我尝试了ResultSet.TYPE_FORWARD_ONLY、ResultSet.CONCUR_READ_ONLY,但没有任何效果。 - Levent Tokmak
首先感谢您的回答。我更改了代码,并没有将查询结果分配给任何变量。我进行了分析,发现java.sql.Statement类分配了206 MB。然后进入Weblogic控制台,暂停连接池并运行GC。我看到内存恢复正常(释放了206 MB)。所以,我的问题是如何在不关闭连接的情况下释放内存。 - Levent Tokmak
如果GC返回了内存,则表示java.sql.Statement没有持有该内存。根据上面的证据,我怀疑是List<Msisdn> 在占用内存,除非“挂起”连接池意味着关闭连接,而这只会发生在“forceSuspend()”而不是“suspend()”,这是根据Weblogic文档所述:http://docs.oracle.com/cd/E13222_01/wls/docs81/jdbc/programming.html#1055751 如果您可以使用Spring的RowCallbackHandler来处理每一行,而不是创建一个List,您可能能够减少内存使用。 - jbindel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接