Memory 何时从堆中释放可变状态值?

Memory 何时从堆中释放可变状态值?,memory,f#,heap,mutable,object-lifetime,Memory,F#,Heap,Mutable,Object Lifetime,在F#WikiBook的第节下,有以下代码片段 > let incr = let counter = ref 0 fun () -> counter := !counter + 1 !counter;; val incr : (unit -> int) > incr();; val it : int = 1 > incr();; val it : int = 2 > incr();; val it : i

在F#WikiBook的第节下,有以下代码片段

> let incr =
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter;;

val incr : (unit -> int)

> incr();;
val it : int = 1

> incr();;
val it : int = 2

> incr();;
val it : int = 3
起初,似乎很容易接受这样一个事实:每次调用
incr
时,可变
计数器的值都会递增

但在考虑了一段时间后,我不明白的是
计数器
何时从堆中释放,以及
计数器
如何在递增之前仍然引用上一个值。存在于
incr
函数范围内的
计数器如何通过多个函数调用生存

因此,主要问题是:

  • 计数器何时从堆中释放
  • 计数器
    不是内存泄漏吗

当增量不再可访问时,计数器将从堆中释放。这不是因为垃圾收集造成的内存泄漏。

词法范围(名称在程序文本中具有含义)和生命周期(对象创建和销毁之间的运行时持续时间)之间的区别有时可能会令人混淆,因为这两者通常高度相关。但是,本例演示的技术在函数式语言中很常见:为实现细节提供一个小的词法范围(对调用方隐藏实现细节),但通过在闭包中捕获来延长其生命周期(这样它的生命周期就变成了封闭对象的生命周期——在本例中是“incr”函数)。这是函数式编程中进行封装的一种常见方法(与面向对象编程中常用的类中公共/私有的封装技术相比)

现在,在这个特定的例子中,“incr”看起来像是一个顶级函数,这意味着它的值在程序的生命周期内持续(如果输入fsi.exe,则为交互式会话)。您可以称之为“泄漏”,但这取决于意图。如果您在整个程序的整个生命周期中都需要某个唯一的id计数器,那么您必须将该计数器可变存储在整个程序的某个位置。因此,这是“泄漏”还是“按设计特性”,取决于“incr”的使用方式(您是否需要在整个程序的其余部分使用该功能?)。在任何情况下,这里的关键点是“incr”拥有内存资源,因此如果您不会永远需要这些资源,您应该安排“incr”引用的闭包在不再需要时变得不可访问。通常,这可能是通过使其位于某个其他函数的本地,例如

let MyComplicatedFuncThatNeedsALocalCounter args =
    let incr = 
        // as before
    // other code that uses incr
    // return some result that does not capture incr

在这种情况下,
incr
是一个顶级函数(如果我没有弄错的话,它作为一个静态字段实现)。它持有一个闭包,而闭包又引用了名为
counter
的ref单元格。只要这个闭包存在,
ref
单元格就会保存在内存中


现在这个顶级绑定确实永远不会被垃圾回收,因为它是一个静态只读字段(用C#术语)。但是,如果您有这样的具有有限生存期的闭包(绑定在本地或对象中),当对闭包进行垃圾收集时,
ref
单元格将被释放。

是的,但这没有帮助。即使在GC环境中也可能发生内存泄漏。也就是说,当您意外保留不再使用的对象时。(例如,实例方法的委托保留
对象)@Dave我正在运行来自FSI的示例。这是否意味着在我退出FSI之前once不会超出范围?是的,在FSI中,这将在FSI会话的整个生命周期中一直存在。如果您希望能够“放开它”,那么请将“incr”变为可更改的,或一个ref,或其他稍后可以“null out”的内容。一般来说,FSI会话往往会泄漏很多信息,因为顶级名称永远不会解除绑定(即使它们由于阴影而无法访问)。