GC.Collect()和Finalize

时间:2012-12-19 14:46:52

标签: c# .net garbage-collection finalizer

好的,众所周知,当GC将对象标识为垃圾时,它会隐式调用对象上的Finalize方法。但如果我做GC.Collect()会怎样?终结者还在执行吗?也许是一个愚蠢的问题,但是有人问我这个,我回答“是”,然后我想:“这完全正确吗?

4 个答案:

答案 0 :(得分:75)

  

好的,众所周知,当GC将对象标识为垃圾时,GC会隐式调用对象的Finalize方法。

不不不。这不是已知的,因为为了知识,语句必须是 true 。该声明是 false 垃圾收集器不会跟踪的终结器,无论是自行运行还是调用Collect终结器线程在跟踪收集器找到垃圾后运行终结器,并且对Collect的调用发生异步。 (如果它发生了,它可能不会,正如另一个答案所指出的那样。)也就是说,在控制从Collect返回之前,你不能依赖终结器线程执行。

这是一个过于简单的草图,说明它是如何工作的:

  • 当集合发生时,垃圾收集器跟踪线程会跟踪根 - 已知的对象以及它们引用的每个对象,等等 - 来确定死对象。
  • 具有挂起终结器的“死”对象被移动到终结器队列上。 终结器队列是根。因此,那些“死”的对象实际上仍然活着
  • 终结器线程(通常是与GC跟踪线程不同的线程)最终运行并清空终结器队列。然后这些对象真正死了,并在跟踪线程的 next 集合中收集。 (当然,因为他们刚刚在第一次收藏中幸存下来,他们可能会在更高的一代。)

正如我所说的那样,过于简单;终结器队列如何工作的确切细节比这复杂一点。但它足以解决这个问题。这里的实际结果是你不能假设调用Collect也运行终结器,因为它没有。让我再重复一遍:垃圾收集器的跟踪部分运行终结器Collect只运行收集机制的跟踪部分。

如果要保证所有终结器都已运行,请在调用WaitForPendingFinalizers后调用名为Collect的名称。这将暂停当前线程,直到终结器线程到处清空队列。如果你想确保那些已完成的对象有回收的内存,那么你将不得不打电话给Collect一个第二时间。

当然,不言而喻,您只应该为调试和测试目的而这样做。如果没有真正的真的充分的理由,就不要在生产代码中做这些废话。

答案 1 :(得分:10)

是的,但不是马上就行。摘录摘自Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework (MSDN Magazine)(*)

  

“当应用程序创建新对象时,new运算符将分配   来自堆的内存。如果对象的类型包含Finalize   方法,然后指向对象的指针放在最终化   队列。终结队列是受控的内部数据结构   由垃圾收集器。队列中的每个条目都指向一个对象   应该在对象的内存之前调用Finalize方法   可以回收。

     

当GC发生时......垃圾收集器扫描完成   队列寻找这些对象的指针。找到指针后   指针将从终结队列中删除并附加到   可释放的队列(发音为“F-reachable”)。可分离的队列是   另一个由垃圾收集器控制的内部数据结构。   可分离队列中的每个指针都标识一个对象   准备将其Finalize方法调用。

     

有一个特殊的运行时线程专用于调用Finalize   方法。当可释放队列为空时(通常是   case),这个线程睡觉了。但是当条目出现时,这个线程会唤醒,   从队列中删除每个条目,并调用每个对象的Finalize   方法。因此,您不应在Finalize中执行任何代码   关于正在执行的线程的任何假设的方法   码。例如,避免访问中的线程本地存储   最终确定方法。“

(*)自2000年11月起,事情可能发生了变化。

答案 2 :(得分:5)

当收集垃圾时(无论是响应内存压力还是GC.Collect()),需要完成的对象都会被置于最终确定队列中。

除非你调用GC.WaitForPendingFinalizers(),否则终结器可能会在垃圾收集完成后很长时间内在后台继续执行。


顺便说一下,无法保证终结者将被称为 。来自MSDN ...

  

Finalize方法可能无法运行完成或可能无法运行   所有在以下特殊情况下:

     
      
  • 另一个终结器无限期地阻塞(进入无限循环,尝试获得它永远无法获得的锁等等)。因为   运行时尝试运行终结器以完成其他终结器   如果终结器无限期阻塞,则可能无法调用。
  •   
  • 该过程终止,而不给运行时提供清理的机会。在这种情况下,运行时的第一个进程通知   终止是DLL_PROCESS_DETACH通知。
  •   
     

运行时仅在关闭期间继续完成对象的Finalize   可终结对象的数量继续减少。

答案 3 :(得分:0)

这里有几点值得说明。

Finalizer是.net对象可以释放非托管资源的最后一点。 仅当您未正确处理实例时才会执行终结器。 理想情况下,终结器绝不应该在很多情况下执行。因为正确的部署实现应该suppress the finalization

Here is an example for correct IDispoable Implementation

如果您调用任何一次性对象的Dispose方法,它应该清除所有引用并禁止最终确定。如果有任何不太好的开发人员忘记调用Dispose方法,那么Finalizer就是救生员。