持久收集垃圾收集?

时间:2012-06-14 16:33:08

标签: clojure

如果我持有对持久集合的一部分的引用,整个集合是否可以被垃圾收集?我能理解这一点吗?

函数gctest只是测试集合的行为。

(defn gctest
  "A shot about testing the gc-ability of persistent thingies."
  [n]
  (take 5 (drop 100 (vec (range n)))))

main=> (def a (gctest 1e7))
main=> (def b (gctest 1e7))
main=> (def c (gctest 1e7))
main=> (def d (gctest 1e7))
main=> (def e (gctest 1e7))
main=> (def f (gctest 1e7))
main=> (def g (gctest 1e7))

OutOfMemoryError GC overhead limit exceeded  clojure.lang.ArrayChunk.dropFirst     (ArrayChunk.java:54)

我已经听说过头部保留,但这似乎有点笼统或不是吗?

我想要了解的是如何使用大变化的收藏品。我希望收藏品的大部分内容会随着时间的推移而发生变化,原则上大部分可以被垃圾收集,但不是全部收集。

有没有一种标准的方法可以解决这个问题?

2 个答案:

答案 0 :(得分:7)

标准GC规则仍然存在:只要您保留对集合的一部分的引用,您的引用中可访问的所有对象都将保留在内存中。因此只能>来自您的参考文献的 ,其余的将被收集。特别是,如果您引用100个元素列表的最后50个元素,将收集前50个元素,其余元素将保留在内存中。

但是,在您的情况下,将保留从100开始的每个集合的所有元素。其原因是懒惰评估。函数take生成 lazy 序列(在您的情况下)5个元素。懒惰的序列对象本身不是真正的序列,而是特殊的生成器对象(虽然它不是Clojure的,而是Python的术语)。 当你需要延迟序列的元素时,生成器对象会生成并返回它。但是如果你不要求元素,生成器只会保留对生成元素所需的所有对象的引用。

在您的示例中,您创建了大型向量并从中请求了5个元素,然后将结果保存到变量abc等.Clojure生成大型向量和生成器对象,指向第100个元素。对集合本身的引用将丢失,但对生成器对象的引用将保存在顶层。您永远不会评估生成器对象,因此永远不会生成真正的5元素序REPL引用变量abc等,这些变量引用生成器对象,生成器对象引用它们生成真实5元素序列所需的集合。因此,所有集合中的所有元素(除了可能是前100个)都必须留在内存中。

另一方面,如果评估生成器对象,它们将生成真正的5个元素序列,并忘记引用其余的集合。试试这个:

user> (def a (gctest 1e7))
#'user/a                                                                                                                                               
user> (println a)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def b (gctest 1e7))
#'user/b                                                                                                                                               
user> (println b)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def c (gctest 1e7))
#'user/c                                                                                                                                               
user> (println c)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def d (gctest 1e7))
#'user/d                                                                                                                                               
user> (println d)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def e (gctest 1e7))
#'user/e                                                                                                                                               
user> (println e)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def f (gctest 1e7))
#'user/f                                                                                                                                               
user> (println f)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def g (gctest 1e7))
#'user/g                                                                                                                                               
user> (println g)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def h (gctest 1e7))
#'user/h                                                                                                                                               
user> (println h)
(100 101 102 103 104)                                                                                                                                  
nil                                                                                                                                                    
user> (def i (gctest 1e7))
#'user/i                                                                                                                                               
user> (println i)
(100 101 102 103 104)                                                                                                                                  
nil 

没有OutOfMemory! Vars abc等现在存储5个元素的真实列表,因此不再有大型集合的引用,因此可以收集它们。

答案 1 :(得分:4)

使用惰性序列可以解决这些问题。在你的情况下,你使用了vec函数,它通过遍历range函数生成的每个元素(范围返回一个惰性序列)实际在内存中创建一个向量。

下面的代码(没有vec调用就没有内存问题)

(defn gctest
  "A shot about testing the gc-ability of persistent thingies."
  [n]
  (take 5 (drop 100 (range n))))

<强>更新

当您使用vec调用时,内存中的所有元素都将被保留而不会被GC收集,因为这些元素是由函数gctest返回的序列对象引用(和必需)的这样当序列对象被要求提供元素时,它可以获取所需的元素(即跳过100个元素并获取5个元素)。