我将以Scala示例为例,但这很可能会影响其他允许命令式和功能式混合的语言。

这是一个简短的示例( UPDATED ,请参见下文):

def method: Iterator[Int] {
    // construct some large intermediate value
    val huge = (1 to 1000000).toList
    val small = List.fill(5)(scala.util.Random.nextInt)
    // accidentally use huge in a literal
    small.iterator filterNot ( huge contains _ )
}

现在iterator.filterNot可以很懒惰地工作了,太好了!结果,我们希望返回的迭代器不会消耗太多内存(实际上是O(1))。但是,可悲的是,我们犯了一个可怕的错误:由于filterNot是惰性的,因此它保留了对函数文字huge contains _的引用。

因此,尽管我们认为该方法在运行时将需要大量内存,并且该方法终止后可以立即释放该内存,但实际上,内存一直处于阻塞状态,直到我们忘记返回的Iterator为止。

(我只是犯了一个错误,花了很长时间才找到它!您可以在堆转储中发现此类错误……)



似乎唯一的解决方案是仔细检查在作用域末尾仍然有效并且捕获了中间变量的函数文字。如果您要构造一个非严格的集合并计划返回它,这会有些尴尬。谁能想到一些不错的技巧,无论是Scala还是其他方式,都可以避免此问题并让我编写漂亮的代码?

更新:我之前给出的示例很愚蠢,如huynhjl的以下答案所示。曾经是:
def method: Iterator[Int] {
    val huge = (1 to 1000000).toList // construct some large intermediate value
    val n = huge.last                // do some calculation based on it
    (1 to n).iterator map (_ + 1)    // return some small value
}

实际上,既然我对这些东西的工作原理有了更好的了解,那么我就不用担心了!

最佳答案

您确定您没有简化测试用例吗?这是我运行的:

object Clos {
  def method: Iterator[Int] = {
    val huge = (1 to 2000000).toList
    val n = huge.last
    (1 to n).iterator map (_ + 1)
  }

  def gc() { println("GC!!"); Runtime.getRuntime.gc }

  def main(args:Array[String]) {
    val list = List(method, method, method)
    list.foreach(m => println(m.next))
    gc()
    list.foreach(m => println(m.next))
    list.foreach(m => println(m.next))
  }
}

如果我理解正确,因为即使在main调用之后gc()仍在使用迭代器,因此JVM将保留huge对象。

这是我的运行方式:
JAVA_OPTS="-verbose:gc" scala -cp classes Clos

这是它最终显示的内容:
[Full GC 57077K->57077K(60916K), 0.3340941 secs]
[Full GC 60852K->60851K(65088K), 0.3653304 secs]
2
2
2
GC!!
[Full GC 62959K->247K(65088K), 0.0610994 secs]
3
3
3
4
4
4

所以在我看来,好像huge对象已被回收...

10-04 21:41