Scala中大型CSV读取超出GC开销限制

时间:2013-04-12 00:01:33

标签: scala garbage-collection jvm sbt

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

我已经定义了一个读取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)}

}

谢谢!

2 个答案:

答案 0 :(得分:6)

首先,如果你不是要求运行enable forking或者增加sbt的内存限制,请删除javaOptions设置。分叉在这里可能是个好主意,所以你不要将你的程序的内存使用行为与sbt混合使用。

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

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

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

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

另外,我从未和scala.io.Source好运。如果你确实已经分配了足够的内存,它仍然会失败,你可能想尝试使用Java的IO库。

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

答案 1 :(得分:0)

尝试返回ret并将其映射到包装方法中。这应该避免让一切都在本地记忆中。