scala - 意外的 Scala 集合内存行为

标签 scala memory-management collections stream

以下 Scala 代码(在 2.9.2 上):

var a = ( 0 until 100000 ).toStream
for ( i <- 0 until 100000 )
{
    val memTot = Runtime.getRuntime().totalMemory().toDouble / ( 1024.0 * 1024.0 )
    println( i, a.size, memTot )

    a = a.map(identity)
}

在循环的每次迭代中使用越来越多的内存。如 a定义为 ( 0 until 100000 ).toList ,那么内存使用是稳定的(give or take GC)。

我知道流会懒惰地评估,但一旦生成就保留元素。但似乎在我上面的代码中,每个新流(由最后一行代码生成)都以某种方式保留了对先前流的引用。有人可以帮忙解释一下吗?

最佳答案

这是发生的事情。 Stream总是懒惰地评估,但已经计算的元素被“缓存”以备后用。懒惰的评估是至关重要的。看这段代码:

a = a.flatMap( v => Some( v ) )

虽然看起来你在改造一个 Stream到另一个并丢弃旧的,这不是发生的事情。新款Stream仍然保留对旧的引用。那是因为结果 Stream不应该急切地计算底层流的所有元素,而是按需计算。以此为例:
io.Source.fromFile("very-large.file").getLines().toStream.
  map(_.trim).
  filter(_.contains("X")).
  map(_.substring(0, 10)).
  map(_.toUpperCase)

您可以根据需要链接任意数量的操作,但读取第一行几乎没有触及文件。后续的每个操作都只是包装了之前的 Stream ,持有对子流的引用。您索取的那一刻 size或做 foreach ,评测开始。

回到你的代码。在第二次迭代中,您创建第三个流,持有对第二个流的引用,而后者又保留对您最初定义的流的引用。基本上你有一堆相当大的物体在生长。

但这并不能解释为什么内存泄漏如此之快。关键部分是... println() , 或 a.size准确地说。不打印(从而评估整个 Stream )Stream仍然“未评估”。未评估的流不缓存任何值,所以它非常 slim 。由于彼此之间不断增长的流链,内存仍然会泄漏,但速度要慢得多。

这引出了一个问题:为什么它适用于 toList这很简单。 List.map()热切地创造新的List .时期。前一个不再被引用并且符合 GC 的条件。

关于scala - 意外的 Scala 集合内存行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14897173/

相关文章:

c# - 在 C# 中获取内存中下一个对象大小的空间

memory - RAM内存中的链表

c++ - C++ std::string 堆内存分配的保证?

collections - Clojure:将 nil 插入集合的内置或单行解决方案

Java - 具有 LinkedHashMap 属性的集合

scala - 使用 Circe 解码 Normal Class(不是 case class)

scala - 我可以在Scala中对类型参数执行匹配以查看其是否实现了特征吗?

php - 拉维尔 : How do I add key and value into Collection or array

scala - 在 Scala 中制作更多 "functional"代码以使用不可变集合

scala - 在解释器中蒸发 Predef.any2stringadd