GC.Collect()执行后是否会立即运行垃圾回收?

6
这个问题只是为了研究目的。
我读过很多关于C#的书,但这个问题总是让我困惑。我所理解的是,C#是托管代码,所有垃圾回收都是在CLR决定何时运行垃圾回收时发生的。我们开始吧。
假设我有一个简单的类Student
public class Student
{
    public int IdStudent { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
}
class Program
{
      static void Main(string[] args)
      {
This is row1:    Person person = new Person() {IdPerson=1, Name="Bill", SurName="Collins"};
This is row2:    System.GC.Collect();
This is row3:    string str="Hello World!";
    }        
}

请审批或拒绝我的假设:
  1. 我对于垃圾回收不会立即在row2运行的说法正确吗?
  2. GC.Collect()只是一个请求,用于触发垃圾回收,但并不会立即在row2处运行。该行的执行可能需要x毫秒/秒。在我看来,方法System.GC.Collect();只是告诉垃圾收集器应该运行垃圾回收,但真正的垃圾回收可能需要x毫秒/秒才会发生

  3. 只有垃圾收集器知道何时运行垃圾回收。如果第二行row2: System.GC.Collect();中的Generation 0有足够的空间,则垃圾回收将不会发生。

  4. 由于我们是在托管环境下编程,因此无法立即运行垃圾回收,只有CLR决定何时运行垃圾回收。垃圾回收可以在x毫秒/秒内运行,也可能因为在调用GC.Collect()方法后,Generation 0有足够的空间来创建新对象而不运行垃圾回收。程序员所能做的就是通过方法GC.Collect()请求CLR运行垃圾回收。

更新:

我已经阅读了这篇关于GC.Collect Method ()的msdn文章。然而,对于未引用对象的真正清除何时开始仍然不清楚。MSDN说:

GC.Collect Method () 强制立即回收所有代的垃圾。

然而,在备注中,我读到了这个:

使用此方法尝试回收所有不可访问的内存。它执行所有代的阻塞垃圾回收。

  1. 我被这个"使用此方法尝试"所困惑,我认为垃圾回收可能不会发生,因为CLR决定有足够的空间来创建新对象。我是正确的吗?

1
它肯定能运行,但你是在问它是否收集了特定的东西吗? - Sayse
@Sayse,嗯,GC会收集未被引用的对象吗? - StepUp
GC.Collect文档中包含了一些答案。 - user2864740
7
除了极少数情况下(主要是微基准测试),如果在代码中发现了 GC.Collect(),那么你做错了什么。 - Damien_The_Unbeliever
你提出的所有问题都与你与GC签订的合同无关。不同的GC实现可以自由地做任何他们想做的事情 :) 这是有原因的-它是运行时中可以从优化中受益很多的部分,而且在合同中有的东西越多,你可以合法进行的优化就越少。如果你在生产代码中的任何地方使用GC.Collect,那么你会破坏一些东西。只需...别这么做。此外,在调试器中运行时,person在你的GC.Collect点被认为未引用。 - Luaan
显示剩余3条评论
2个回答

5

简短回答

调用GC.Collect()会进行完整的垃圾回收并等待其完成,但它不会等待任何挂起的终结器运行。

详细回答

你的假设部分正确,因为运行终结器的GC在一个或多个后台线程中运行。(但请参见本答案末尾的脚注。)

然而,你可以通过在调用GC.Collect()之后调用GC.WaitForFullGCComplete()GC.WaitForPendingFinalizers()来等待完整的GC完成:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForFullGCComplete();

请注意,运行终结器的线程是未指定的,因此不能保证该方法会终止。

请注意,通常不应使用此方式使用垃圾回收;我假设您有一个需要解决的特殊情况,或者您正在进行研究。

我看到的唯一有效用例是在应用程序关闭时,您想要(尝试)确保所有终结器都已运行-例如,它们将刷新日志文件等。

如上所述,这仍然不能保证运行所有终结器;这只是你能做的最好的事情。

回答您的第五点:

GC.Collect()的文档说明如下:

强制对所有代进行立即垃圾回收。

因此,这将强制进行垃圾回收。

文档还指出:

使用此方法尝试回收所有不可访问的内存。

在那里使用“尝试”这个词只是意味着即使运行了完整的GC,也不一定会重新获取所有不可访问的内存。这可以发生几种原因,例如终结器可能会阻塞。

脚注

.Net 4.5 允许您指定GC.Collect()是否阻塞

事实上,GC.Collect()的文档说明对所有代执行阻止垃圾回收,这似乎与我上面的说法相矛盾。然而,关于是否真的是这种情况似乎存在一些混淆。

例如,请参见此线程

答案是:默认情况下,GC.Collect()将等待所有代进行垃圾回收,但不会等待待处理的终结器,这些终结器始终在单独的线程中执行。

因此,如果您不需要等待终结器,则仅需要调用GC.Collect(),而不需要等待其他任何内容。


此问题仅用于研究目的。请在我的问题的“更新”部分第五个问题回答。 - StepUp
只需要回答“是”或“否”,GC.Collect()调用后不会立即强制进行垃圾回收。 - StepUp
2
@StepUp GC.Collect()会强制立即进行垃圾回收并等待其完成(完全垃圾回收),但除了尚未完成的终结器,这些终结器在单独的线程中运行。我在我的答案开头加了一些内容来强调这一点。 - Matthew Watson

1
有两个垃圾回收器,从您的代码来看,我认为您想了解工作站GC。它通过在完整收集期间并发运行来最小化暂停时间。工作站GC使用第二个处理器并发运行收集,最大程度地减少延迟同时降低吞吐量。只有在服务器GC未能正常执行其工作时,我们才应该担心GC行为。如果按照工作站GC的要求在代码中添加GC.collect(),则在服务器GC上可能是无用的。
服务器GC旨在实现最大吞吐量,并具有非常高的性能扩展性。服务器上的内存碎片问题比工作站上更严重,因此垃圾回收是一个有吸引力的选择。在单处理器场景中,两个收集器以相同方式工作:工作站模式,没有并发收集。
“我对这个方法感到困惑,试着使用它,我认为垃圾收集可能不会发生,因为CLR决定已经有足够的空间创建新对象。我是正确的吗?”
对于工作站GC,GC.Collect将尽快开始收集,您可以安全地假设它立即进行收集。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接