1

所以。我正在使用 Scala,而且我对它比较陌生(主要是 python 人)。我正在通过 sbt 编译和运行我的代码。我在一个 Ubuntu 机器上,目前正在运行 Java 6。我有两个 CSV;我需要接受它们,处理它们,然后操纵它们。每个 CSV 约为 250mb;如果这可行,我可能会用更大的 CSV 重复这个过程。

我已经定义了一个读取 CSV 并将每一行写入我需要的数据结构的函数。我在每个 CSV 上串联调用此函数。问题是:它为第一个 CSV 完美返回(并且非常快),但第二个总是抛出java.lang.OutOfMemoryError: GC overhead limit exceeded错误。

我已经尝试了很多东西。我的build.sbt定义javaOptions += "-Xmx20480m -XX:+HeapDumpOnOutOfMemoryError";我也尝试过使用-XX:-UseGCOverheadLimit,但这似乎没有任何帮助。根据我一直在阅读的 Java 文档,该错误表明大量系统资源正在用于垃圾收集 - 但坦率地说,我不清楚它是什么垃圾收集,或者如何减少它。我假设我的函数一定是......在某处泄漏内存,或者我一定是误用了 Scala,但我不知道怎么做。

这是我的功能:

def readAndProcessData(path: String) = {
    val fileLines = Source.fromFile(path).getLines.drop(1)
    val ret = mutable.Map[String, List[Tuple2[String, String]]]()

    def addRowToRet(row: String) = {
        val rowArray = row.split(",")
        if (!(ret contains rowArray(0))) {
            ret.update(rowArray(0), List[Tuple2[String, String]]())
        }
        ret(rowArray(0)) = Tuple2(rowArray(1), rowArray(2)) :: ret(rowArray(0))
    }

    for (row <- fileLines) {
        addRowToRet(row)
    }

    ret.map{tup => (tup._1 -> tup._2.sorted)}

}

谢谢!

4

2 回答 2

6

首先,如果您不分叉运行,请启用分叉或提高 sbt 的内存限制并删除 javaOptions 设置。在这里分叉可能是一个好主意,因此您不会将程序的内存使用行为与 sbt 的内存使用行为混为一谈。

您还应该关闭正在创建的 Source 对象,以确保释放其资源。

它是否在一致的地方崩溃,例如在排序时?或者崩溃是否发生在代码中相当随机的位置?

我假设您正在阅读的文件采用 ASCII 或 UTF8 等编码,其中大多数到所有字符都用 8 位表示。Java 使用每个字符 16 位,因此请记住,通过将其读入 Java 字符串,大小会增加一倍以上(“超过”是由于其他开销)。这本身不应该把你推倒,但这意味着当你加载两个 250MB 的文件时,你可能会消耗超过 1GB 的内存来存储数据。

您的密钥相对于文件中的行数如何分布?换句话说,您的地图中是否有几乎每一行、大约一半、四分之一等的条目?你可能有一个相当大的地图(就条目而言),当你对它执行“地图”操作以对值进行排序时,你最终会在内存中得到其中两个,直到函数返回并且旧的成为可收集的. 您可能还想尝试使用不可变映射或围绕 Java 可变映射的包装器。有时 Scala 的可变数据结构不如它们的不可变数据结构那么健壮。

此外,我对 scala.io.Source 从来没有好运。如果在你相当确定你实际上分配了足够的内存后它仍然失败,你可能想尝试使用 Java 的 IO 库。

最后,如果检查一些设置并稍微检查它不起作用,您应该将内存分析器连接到它,例如VisualVM。那是您有机会找出问题的真正所在,而不是通过修改进行猜测和检查。

于 2013-04-12T14:02:15.333 回答
0

尝试返回 ret 并将其映射到包装方法中。那应该避免将所有内容都放在内存中。

于 2013-04-12T09:42:49.203 回答