Sco*_*son 7 scope scala function-literal lazy-evaluation
我会用Scala示例来问这个问题,但很可能这会影响其他允许混合命令和函数样式的语言.
这是一个简短的例子(更新,见下文):
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 _ )
}
Run Code Online (Sandbox Code Playgroud)
现在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
}
Run Code Online (Sandbox Code Playgroud)
事实上,现在我对这些事情的运作方式有所了解,我并不那么担心!
你确定你没有过度简化测试用例吗?这是我运行的:
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))
}
}
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,因为main即使在gc()调用之后也使用迭代器,JVM将保留在huge对象上.
这是我运行它的方式:
JAVA_OPTS="-verbose:gc" scala -cp classes Clos
Run Code Online (Sandbox Code Playgroud)
这是它打印到最后:
[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
Run Code Online (Sandbox Code Playgroud)
所以它看起来好像这些huge物品被回收了......