标准 GC 规则仍然存在:只要您保留对集合的一部分的引用,从您的引用访问的所有对象都保留在内存中。因此,只有可以从您的引用中访问的集合部分将被保留,其余部分将被收集。特别是,如果您引用 100 元素列表的最后 50 个元素,则将收集前 50 个元素,其余的将保留在内存中。
但是,在您的情况下,将保留从第 100 个开始的每个集合的所有元素。其原因是懒惰的评价。函数take
产生(在你的情况下)5个元素的惰性序列。惰性序列对象本身不是真正的序列,而是特殊的生成器对象(虽然它不是 Clojure 的,而是 Python 的术语)。当您需要惰性序列元素时,生成器对象会生成并返回它。但是如果你不要求元素,生成器只会保留对它可能需要生成元素的所有对象的引用。
在您的示例中,您创建大向量并从中请求 5 个元素,然后将结果保存到变量a
、b
、c
等。Clojure 生成大向量和生成器对象,指向第 100 个元素。对集合本身的引用丢失,但对生成器对象的引用保存在顶层。您永远不会评估生成器对象,因此永远不会制作真正的 5 元素序列。REPL 指的是 vars a
、b
、c
等,这些 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 个元素的真实列表,因此不再有对大型集合的引用,因此可以收集它们。