阅读最近的一个问题,我确定了正在讨论的功能
(def fib-seq
(lazy-cat [0 1] (map + (rest fib-seq) fib-seq)))
保持在一个序列的头部,但我突然想到我的答案,我已经掩盖了细节,就像他们显而易见的那样,所以我回去澄清并且说得很短。我知道fib-seq是一个var,只要它在它周围就能保存序列中的所有元素,但我根本不清楚序列是如何被保持的确切机制。任何澄清将不胜感激。
答案 0 :(得分:12)
基本上,常规GC规则适用...序列只是一个对象并且保持其头部意味着保持对该对象的引用。这需要保留尽可能多的内存序列,因为Clojure序列是缓存的。
(更详细的解释如下 - 请参阅粗体片段以获取它的要点......; - ))
Clojure中的'序列'是一个实现ISeq接口的对象。这提供了提取序列的第一个元素和序列的其余部分(实现ISeq的另一个对象)的方法。作为一个关键细节,这些不仅关注计算正确的对象(序列的第一个/其余部分)并将其返回给调用者,而且还要将计算值缓存在内存中,以便后续请求更快 - 而且更多重要的是,所有对序列的相同元素的所有请求都保证返回相同的值,即使ISeq是在可变Java对象之上生成的,该对象在某个时刻发生了变化。 (注意这对Clojure序列的不可变语义绝对至关重要。)
另一方面,Var是一个容器,它粗略地保存了一些Java对象的“指针”。如果这恰好是一个ISeq,那么只要Var本身不是垃圾收集(如果它是当前存在的命名空间中的顶级var,它显然不会是这样)或者反弹,ISeq本身不会被垃圾收集,特别是它用于缓存第一个/其余序列的内存将不会被释放。
至于序列的其他元素:绑定到Var的ISeq的“rest”是一个ISeq本身。此外,它由第一个ISeq缓存。因此,绑定到Var的ISeq的“rest”ISeq的第一个元素永远不会被垃圾收集,因为对它的引用由绑定到Var的ISeq的“rest”ISeq持有,而这个ISeq不会是GC'd,因为它被ISeq缓存为'rest'组件被绑定到Var,只要它被绑定到Var,它反过来就不会是GC,而var通常永远不会GC'd,因为它是名称空间中的顶级Var。
显然,如果Var 不再被其命名空间(ns-unmap
)保留或者命名空间本身被抛出(remove-ns
),那么它将成为GC。如果它恰好持有了一个ISeq,那么当且仅当它不被其他一些代码保存时,该ISeq将被GC化 - 当然通常的GC规则适用。对于binding
引入的绑定和let
引入的本地绑定,以上所有内容都适用于绑定的模数生存期问题。 (这不是本Q的主题。)