8

如果我持有对持久集合的一部分的引用,整个集合可以被垃圾回收吗?我理解正确吗?

函数 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)

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

我想了解的是如何使用大变化的集合。我预计收集的大部分会随着时间的推移而变化,原则上大部分可以被垃圾收集,但不是全部。

有没有标准的方法来处理这个问题?

4

2 回答 2

7

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

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

在您的示例中,您创建大向量并从中请求 5 个元素,然后将结果保存到变量abc等。Clojure 生成大向量和生成器对象,指向第 100 个元素。对集合本身的引用丢失,但对生成器对象的引用保存在顶层。您永远不会评估生成器对象,因此永远不会制作真正的 5 元素序列。REPL 指的是 vars abc等,这些 vars 指的是生成器对象,而生成器对象指的是它们产生真正的 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 

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

于 2012-06-14T18:50:20.627 回答
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 个元素)当序列对象被要求提供元素时。

于 2012-06-14T16:43:29.533 回答