CLR垃圾收集器会主动遍历已创建的所有对象,如果正在使用它们,则会计算出来。但是,垃圾收集器如何决定要杀死哪个对象以及哪些对象正在使用?
我理解为对象分配空值的概念就足够了。但是,如果我只写
怎么办?string obj = new string(new char[] {'a'});
而不是空分配线obj = null;
。
垃圾收集器如何确定何时清洁它?
答案 0 :(得分:1)
CLR垃圾收集器(在其核心)是一个所谓的跟踪 GC。 (另一个" big"类垃圾收集器是所谓的引用计数 GC。)
跟踪GC工作,以递归方式和#34;跟踪"来自一组已知可达的对象的可到达对象集。以下是它的工作原理:
假设我们已经有一组我们知道可以访问的对象。对于该集合中的每个对象,请遵循所有引用(例如字段,以及内部指针,例如class
指针等)。这些对象也可以访问。重复,直到您至少访问过所有对象一次。现在您知道所有可到达的对象。 (我们可以说我们已经根据可达性计算了传递闭包。)所有访问过的>> 的对象都无法访问,因此有资格进行垃圾回收。
现在,我们只需要弄清楚如何启动这个算法,即如何获得已知可以访问的第一个对象集。好吧,每种语言通常都有一组已知可以随时访问的对象。我们从中开始跟踪的集合称为根集。它包括:
unsafe
记忆那就是它。
当然,这个主题有很多变化。此跟踪思想的最简单实现称为 mark-sweep 。它有两个阶段,标记和扫描(duh!)标记阶段是跟踪阶段,您跟踪可到达的对象,然后在对象标题中设置一个位说"是的,可达的"。在扫描阶段,您将收集不设置该位的所有对象,并将该位重置为其他对象中的false
。
该方案的略微改进是保留单独的标记表。例如,您不必在整个RAM中写入,只是为了设置这些标记位(例如,将所有数据从缓存中抛出),如果共享内存,也会触发写入时复制与另一个过程)。其次,您不必访问可到达的对象来重置标记位,您可以在完成后丢弃标记表。
这种方案最大的缺点是它会导致内存碎片化。最大的优点是对象不会在内存中移动,这意味着您可以分发指向对象的指针,而不必担心这些指针可能会变得无效。
另一个非常简单的方案是Henry Baker的半空间复制收集器。它被称为"半空间"因为它总是使用最多50%的已分配内存。它也是一个跟踪收集器,但它是一个复制收集器而不是标记扫描。它不是在访问对象时标记对象,而是将它们复制到空的一半内存中。之后,旧的一半可以在不变的时间内被释放。
优势在于,每次复制对象时,它们都会整齐地紧密包装在没有孔的内存中,因此没有碎片。但是,它们在内存中移动,所以你不能只是指出那些对象的指针。
注意:CLR的垃圾收集器(它实际上有两个!)比我提出的两个方案复杂得多,复杂得多。然而,他们都在追踪地方选区。
收藏家的第二大类是引用计数收藏家。每次创建或销毁引用时,它们不会仅在集合发生时跟踪引用,而是计算引用。因此,当您将对象分配给局部变量或字段,或将其作为参数传递时,...,系统会在对象标头中递增引用计数器,并且每次将不同的对象分配给局部变量,或者局部变量超出范围,或者字段所属的对象获取GCd,...,引用递减。如果引用计数达到0,则不再有引用,并且该对象符合垃圾回收的条件。
这个方案的一大优点是,当一个对象无法访问时,你总能确切地知道。最大的缺点是你可以得到断开连接的循环,其参考计数永远不会是0.如果你有来自 A → B 的参考,那么 B → C ,来自 C → A ,以及 D → B ,然后 A 的引用计数为1, B 的引用计数为2, C &# 39; s引用计数为1.如果您现在从 D 中删除引用, B 的引用计数将下降为1,并且没有来自系统的其余部分为 A 或 B 或 C ,因此它们都无法访问,但它们的引用计数永远不会降至0,所以他们永远不会被收集。
GC的第三个主要思想是代际假设:
事实证明,对于典型系统,几乎所有对象都是如此。这意味着,根据年龄不同对待对象是有意义的。 世代GC 将对象划分为不同的代,并且每个对象具有不同的垃圾收集和内存分配策略。 (让我们离开吧。)
有关垃圾收集的更多信息,请阅读The Garbage Collection Handbook – The art of automatic memory management by Richard Jones, Antony Hosking, Eliot Moss。