【问题标题】:BufferedWriter writing many rows and causing OutOfMemory errorBufferedWriter 写入多行并导致 OutOfMemory 错误
【发布时间】:2018-02-20 11:41:53
【问题描述】:

我有一个类可以从数据库中获取一个非常大的结果集,大约 10,000,000 行,我需要将其写入文件。我有一个记录器,每 10k 行输出一次,但是当它达到数百万行时,它开始呈指数级减速,直到我收到 java.lang.OutOfMemoryError: GC overhead limit exceeded 错误。我也将 -Xmx6000m 作为参数传递给 VM。

有什么方法可以防止上述错误吗?使用 try with resources 方法不是最好的方法吗?我应该使用缓冲写入器以外的方法吗?

File outputFile = new File(incoming.toUri());
StringBuilder outputCSV = new StringBuilder();
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile))) {
    for (RowIterator rowIterator = tradesFromKDB.rowIterator(); rowIterator.hasNext(); count++) {
        outputCSV.setLength(0);
        row = rowIterator.next();

        for (String cell : row.toString().split("\\|")) {
            try {
                outputCSV.append(cell.split("=")[1] + ",");
            } catch (ArrayIndexOutOfBoundsException e){
                // This is a result of the right hand side of an = not having a value
                // E.G. |consolidatedflag=|
                // In such a case a comma is just appended
                outputCSV.append(',');
            }
        }

        if( count % 10000 == 0)
            logger.info("Processed " + count + " rows.");

        // Remove the last to characters; the ending '>' and trailing comma
        outputCSV.setLength(outputCSV.length() - 2);
        outputCSV.append("\n");

        bufferedWriter.write(outputCSV.toString());
    }
} catch (IOException e) {
    logger.error(e.getMessage());
}

编辑

我已经把它去掉了,去掉了很多垃圾,只写了一行:

File outputFile = new File(incomingQPBEOD.toUri());
StringBuilder outputCSV = new StringBuilder();
try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile))) {
    for (RowIterator rowIterator = tradesFromKDB.rowIterator(); rowIterator.hasNext(); count++) {
        row = rowIterator.next();

        if( count % 10000 == 0)
            logger.info("Processed " + count + " rows.");

        bufferedWriter.write(row.toString());
    }
} catch (IOException e) {
    logger.error(e.getMessage());
}

问题仍然存在,所以它要么是 rowIterator 要么是 bufferWriter。

编辑 2

我已删除对 BuffererWriter 的写入,并且内存没有问题。

        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile))) {
            for (RowIterator rowIterator = tradesFromKDB.rowIterator(); rowIterator.hasNext(); count++) {
                row = rowIterator.next();

                if( count % 10000 == 0)
                    logger.info("Processed " + count + " rows.");

//              bufferedWriter.write(row.toString());
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        }

编辑 3

返回的数据总是很相似:

已处理 990000 行。

<Row:989999 date=2018-02-14|time=09:21:01|sym=TEST|price=1000.00|size=0|ex=MSCI|ttype=NONE|execvenue=NONE|extime=Wed Jul 14 09:24:01 GMT 2017|gmdtime=Wed Jul 14 09:21:01 GMT 2017|consolidated_flag=flagtype|trade_flags=Num|extimeNS=2018-02-14 09:21:01.261320525>

它总是有这么多的密钥对值。

如果我将row.toString() 的值分配给变量,问题似乎就来了。

将以下内容分配给 String 会导致减速和崩溃,但简单地将 toString() 值打印到记录器不会导致任何减速。

try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile))) {
            for (RowIterator rowIterator = tradesFromKDB.rowIterator(); rowIterator.hasNext(); count++) {
                Row row = rowIterator.next();

                if( count % 10000 == 0) {
                    logger.info("Processed " + count + " rows.");
                    logger.info(row.toString());
                }

                // declared outside try with resource
                s = row.toString();
//              bufferedWriter.write(row.toString());
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        }

【问题讨论】:

  • 尝试增加 jvm 堆大小。
  • 你在初始化 BufferedWriter 对象时尝试过使用缓冲区大小
  • 为问题添加了缓冲区信息; -Xmx6000m是我通过的
  • 为什么说是缓冲写入器导致OOM。您在这里发生了一些事情:数据库连接(如果它缓存行,就像许多 JDBC 驱动程序和/或 ORM 所做的那样),这可能是真正的原因。它可能是缓冲区(尽管您似乎重置了它),也可能是单行太大...逐个测试和/或配置文件。
  • 谢谢。我认为你甚至可以在没有作者的情况下添加一个版本,看看会发生什么。

标签: java bufferedwriter


【解决方案1】:

问题在于垃圾收集无法有效地恢复堆(意味着 98% 的 CPU 时间用于恢复不到 2% 的堆)。

这可能是因为数据量很大。通常,当请求的缓冲区长度超过缓冲区大小时,BufferedWriter 应该刷新到底层流。

虽然不保证是缓冲区的问题,但也可能是数据库连接,您应该按照 GPI 的建议逐步调试它。

这是一个我从未遇到过的非常有趣的问题。您可能会尝试在本地拆分文件以获得如此大的结果?如果这不可行,那么有时关闭流并重新打开可以通过使 GC 以这种方式恢复更多堆来解决问题。

【讨论】:

  • 此时,没有证据或迹象表明问题出在 BufferedWriter 本身。
  • 你是 100% 正确的。它也可能是数据库连接。 (编辑:或其他任何东西。我根据您的输入进行了更新。)
猜你喜欢
  • 2013-03-22
  • 1970-01-01
  • 2016-02-12
  • 2021-07-29
  • 1970-01-01
  • 2011-02-06
  • 2020-06-30
  • 2020-07-05
  • 1970-01-01
相关资源
最近更新 更多