什么时候对数组进行垃圾回收?

时间:2018-11-08 16:06:43

标签: c# .net garbage-collection

几年前,我通过C#阅读了CLR书,有一天我被问到一个数组是否仍然感到困惑,问题是要弄清楚何时下面的方法中的数组可用于垃圾回收。 :

public static double ThingRatio()
{
    var input = new [] { 1, 1, 2 ,3, 5 ,8 };
    var count = input.Length;
    // Let's suppose that the line below is the last use of the array input
    var thingCount = CountThings(input);
    input = null;
    return (double)thingCount / count;
}

根据此处给出的答案:When is an object subject to garbage collection?指出:

  

一旦不符合要求,他们都将有资格领取   需要了。这意味着在某些情况下,对象可以   甚至在它们所涉及的范围结束之前就被收集   定义。另一方面,实际收集也可能发生   以后。

我倾向于说从第6行(即input = null;)开始,该数组将受到GC的限制,但是我不确定...(我的意思是,在分配之后,肯定不再需要该数组了) ,但也很麻烦,因为它是在CountThings调用之后,但同时需要“空”赋值数组。)

3 个答案:

答案 0 :(得分:6)

记住对象和变量不是同一件事。 变量具有特定方法或类型的范围,但是它所引用或用于引用的对象没有这种概念。这只是一滴记忆。如果GC在input = null;之后但在方法结束之前运行,则该数组只是一个孤立的对象。它不是可访问,因此符合收集条件。

“可到达”(而不是“需要”)是这里的关键词。此行之后不再需要数组对象:var thingCount = CountThings(input);。但是,它仍然可以访问,因此当时无法收集...

我们还需要记住,它不是立即收集的。这是唯一符合条件的产品。实际上,我发现.Net运行时除非确实需要,否则不会倾向于在用户方法中间调用GC。 通常来说,不需要将变量提前设置为null ,在极少数情况下甚至可能有害。

最后,我要补充一点,我们读写的代码与机器实际使用的代码不同。请记住,还有一个编译步骤将所有这些转换为IL,然后是一个JIT流程来创建最终运行的最终机器代码。甚至下一条线的概念也已经是对实际发生情况的抽象。一行可以扩展为实际IL的几行,或者在某些情况下甚至可以重写为包含所有新的编译器生成的类型,例如闭包或迭代器块。因此,这里的所有内容实际上仅是指简单的情况。

答案 1 :(得分:4)

  

GC神话:将对象的引用设置为null将迫使GC立即收集它。
  GC真相:将对象的引用设置为null有时会使GC更快地收集它。

在下面引用我所引用的部分博客文章,并将其应用于您的问题,答案如下:

JIT通常足够聪明,可以意识到可以input = null进行优化。剩下CountThings(input)作为对该对象的最后引用。因此,在该调用之后,input不再使用,并作为GC根目录删除。这样会使对象在内存中变成孤立对象(没有指向它的引用),使其有资格进行收集。当GC实际上要收集它时,是另一回事。

更多信息,请访问To Null or Not to Null

答案 2 :(得分:1)

当对象被认为是现有对象时,不能对其进行垃圾回收。 .NET中将存在一个对象以及对其的任何引用,或者该对象具有已注册的终结器,并且一旦这两个条件均不适用,该对象将不存在。只要对象本身存在,对象中的引用就会存在,并且只要有任何方法可以观察它们,自动变量中的引用就会存在。如果垃圾收集器检测到对没有注册终结器的对象的唯一引用保留在弱引用中,则这些引用将被销毁,从而导致该对象不再存在。如果垃圾收集器检测到对带有已注册终结器的对象的唯一引用保留在弱引用中,则其“ track resurrection”属性为false的任何弱引用都将将该对象的引用放置在一个具有严格根目录的对象列表中需要“立即”完成,并且终结器将不注册(因此,当终结器到达执行点时将无法观察到该对象时,终结器将不存在)。

请注意,某些来源将对象的终结器的触发与垃圾回收相混淆,但是保证终结器被触发的对象至少在该终结器执行之前就一直存在,并且可能无限期地存在终结器完成执行时是否存在对它的任何引用。

在您的示例中,根据CountThings对传入引用的处理方式,可能存在三种情况:

  1. 如果CountThings不在任何地方存储引用的副本,或者在input被覆盖之前覆盖了它存储的引用的任何副本,则该引用将很快不复存在随着input被覆盖或不复存在[自动持续时间变量可能在编译器确定不再观察其值时就不复存在了。]

  2. 如果CountThings将引用的副本存储在返回后继续存在的某个位置,并且最后一个现有引用由弱引用以外的其他任何东西持有,则该对象将不再存在上一个引用被销毁后,就会立即消失。

  3. 如果该数组的最后一个现有引用最终被保存在一个弱引用中,则该数组将继续存在,直到出现第一个GC周期为止,此时弱引用将被清除,从而导致该数组不再存在。请注意,只有在发生GC循环时,才需要缺少对数组的非弱引用。程序可能(但并非不常见)将引用的副本存储到WeakReferenceConditionalWeakTable或其他持有某种形式的弱引用的对象中,销毁所有其他副本,然后在下一个GC周期之前,读出弱引用,以生成该引用的非弱副本。如果发生这种情况,系统既不会知道也不会在乎是否存在引用的非弱副本。但是,如果GC周期发生在引用被读出之前,那么稍后检查弱引用的代码将发现它为空。

一个主要的观察结果是,虽然终结器和弱引用使事情稍微复杂化,但GC销毁对象的唯一方法是使弱引用形式无效。就GC而言,当系统实际上不执行GC周期时,存在的唯一存储类型是存在的对象使用的存储,用于.NET内部目的的存储以及可用于存储的存储区域。满足未来的分配。如果创建了对象,则它所占用的存储将不再是可用于将来分配的存储区域。如果该对象后来不再存在,则包含该对象的 storage 也将以GC知道的任何形式停止存在,直到下一个GC周期。下一个GC周期不会破坏该对象(该对象已经不复存在),而是将包含该对象的存储添加回其可用于添加将来分配的区域列表中(使该存储再次存在)