GC.Collect()和Finalize

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

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

不不不。 这是不知道的,因为为了成为知识,一个陈述必须是真实的 。 那个陈述是错误的垃圾收集器不会在追踪时运行终结器 ,无论它是否运行本身,还是调用Collect在跟踪收集器发现垃圾之后,终结器线程运行终结器 ,并且相对于对Collect的调用asynchronous发生。 (如果它发生在所有,它可能不会,另一个答案指出)。也就是说,不能依赖于控制从Collect返回之前执行的终结器线程。

下面简单介绍一下它是如何工作的:

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

正如我所说,这是过于简单的; 终结者队列如何工作的确切细节比这更复杂一点。 但是这个想法已经足够了。 这里的实际结果是, 你不能认为调用Collect也运行终结器 ,因为它不。 我再重复一次: 垃圾收集器的跟踪部分运行终结器Collect只运行收集机制的跟踪部分。

在调用Collect之后调用适当命名的WaitForPendingFinalizers ,如果你想保证所有的终结器已经运行。 这将暂停当前线程,直到终结器线程清空队列。 如果你想确保那些最终化的对象有回收的内存,那么你将不得不再次调用Collect

当然,不言而喻,你只应该做这个debugging和testing的目的。 不要在生产代码中做这个废话,没有真正的好理由。

其实答案是“视情况而定”。 实际上有一个执行所有终结器的专用线程。 这意味着对GC.Collect调用只会触发这个过程,所有终结器的执行将被asynchronous调用。

如果你想等所有的终结者被调用,你可以使用下面的技巧:

 GC.Collect(); // Waiting till finilizer thread will call all finalizers GC.WaitForPendingFinalizers(); 

是的,但不是直接的。 摘自“ 垃圾收集:Microsoft .NET Framework中的自动内存pipe理”(MSDN Magazine) (*)

“当应用程序创build一个新的对象时,新的操作符从堆中分配内存,如果对象的types包含一个Finalize方法,那么指向该对象的指针将被放置在最终化队列中,最终化队列是一个内部数据结构由垃圾回收器。队列中的每个条目都指向一个对象,该对象在回收对象的内存之前应该调用它的Finalize方法。

当GC发生时…垃圾收集器扫描结束队列,查找指向这些对象的指针。 当find一个指针时,指针将从最终化队列中移除,并附加到可扩展队列(发音为“F-reachable”)。 可变空间队列是垃圾收集器控制的另一个内部数据结构。 可freachable队列中的每个指针标识一个准备好调用Finalize方法的对象。

有一个专用于调用Finalize方法的特殊运行时线程。 当freachable队列是空的(通常是这种情况),这个线程睡觉。 但是当条目出现时,这个线程会唤醒,从队列中移除每个条目,并调用每个对象的Finalize方法。 因此,您不应该在执行代码的线程做出任何假设的Finalize方法中执行任何代码。 例如,避免在Finalize方法中访问线程本地存储。“

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

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

除非调用GC.WaitForPendingFinalizers() ,否则在垃圾回收完成后,终结器可能会在后台继续执行。


顺便说一句,没有保证终结者将被称为。 从MSDN …

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

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

只有当可终结对象的数量持续减less时,运行时才会继续在closures期间对对象进行Finalize。

更多的几点值得在此陈述。

终结者是.net对象可以释放非托pipe资源的最后一点。 只有当你不正确地处理你的实例时才会执行终结器。 理想情况下,终结者绝不应该在很多情况下执行。 因为恰当的处置实施应该压制最终确定 。

这里是一个正确的IDispoable实现的例子 。

如果您调用任何一次性对象的Dispose方法,它应该清除所有引用,并禁止最终化。 如果没有那么好的开发者忘记调用Dispose方法,Finalizer就是救星。