【发布时间】:2021-03-04 05:02:27
【问题描述】:
我需要将从 jdbc ResultSet 提取的约 5000 万行写入 CSV 文件。
写入 CSV 文件的 150 万行大约为 1 GB。
jdbcTemplate.query(new CustomPreparedStatementCreator(arg), new ResultSetExtractor<Void>() {
@Override
public Void extractData(ResultSet rs) {
while (rs.next()) {
// transform each row's data (involves creation of objects)
// write the transformed strings to csv file
}
}
问题是我有一个 8 GB 的堆,它很快就被填满了。
因此,在达到 1000 万行之前,我遇到了 java.lang.OutOfMemoryError。
我的另一个限制是查询读/写超时设置为 30 分钟。
如何回收和重用 JVM 堆内存?
尤其是为我不再需要的对象分配的内存。
我read说强制GC运行并不能保证内存会被回收。
我有哪些选择?我是否应该通过 JNA 或 JNI 将责任交给非 GC 语言
如 C、C++ 或 JNI 来处理 ResultSet?
[编辑] 看来我处境艰难:D 添加更多信息,正如@rzwitserloot 指出的那样
- 我正在从与数据湖挂钩的数据虚拟化工具中读取(仅限 SELECT 查询)数据。
- 数据虚拟化工具的 jdbc 驱动程序确实支持 LIMIT,但查询是由业务设计的,以返回大量数据。所以我可以一次性提取数据并生成 CSV - 这意味着,我无法避免巨大的 SELECT 或放置一个 LIMIT 子句
- 我需要检查这些属性:
resultSetType、resultSetConcurrency、resultSetHoldability。
我已经做了什么:
首先,我使用生产者-消费者模式将 jdbc 获取操作与慢速文件写入操作分开。这有助于在 30 分钟超时之前创建包含 1-5 百万行的 CSV 文件。
其次,我增加了消费者线程的数量,并让它们写入自己单独的部分文件,以便稍后合并到单个 CSV 文件中。这加快了文件写入速度,并在 30 分钟超时之前创建了一个包含 10-2000 万行的 CSV 文件。
我在 ResultSetExtractor 中创建对象并通过有界队列将其传递给消费者线程。一旦将这些对象中的数据写入文件,就不再需要这些对象。
【问题讨论】:
-
你考虑过RowCallbackHandler的使用吗? (mkyong.com/spring/spring-jdbctemplate-handle-large-resultset)
-
我注意到,如果你快速清理堆,GC 可能会太慢而无法释放。 “强制” GC 运行并不能保证它,但
System.gc()对我在这种情况下避免内存问题是有效的,在这种情况下,我放入循环if (Runtime.getRuntime().freeMemory() < someThresholdTweakedForPerformance) { System.gc(); }。从长远来看,这可能不是最佳选择,但它确实有效。 -
@DaveH 我不确定这会有所帮助。如果您查看示例,它会在堆上创建一个 String 对象。
String name = resultSet.getString("Name");这将保留在那里,如果我们从结果集中获得另一个具有相同值的字符串,它将被引用。 -
@DanielWiddis,我实际上正在快速咀嚼堆,这是真的。这分解为大约 6-8 百万行,因为在同一个 JVM 实例上运行着其他任务。因此,调用
System.gc();并不能保证回收内存 - 此任务需要保证能够生成 CSV。 -
字符串真的会留在堆中吗?在循环外部声明变量并在内部重新分配它。 “旧”字符串将立即可用于垃圾收集,因为它没有对它的活动引用。您的问题主要不是 rowsetextractor 是构建一个包含 5000 万行的列表,然后按顺序处理它吗? RowCallBackHandler 将处理从结果集返回的每一行
标签: java jvm java-native-interface jna