45

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

4

5 回答 5

75

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

不不不。那是未知的,因为为了成为知识,陈述必须是真实的。这种说法是错误的。垃圾收集器在跟踪时不会运行终结器,无论它是自行运行还是您调用Collect. 终结器线程在跟踪收集器找到垃圾后运行终结器,并且这与对Collect. (如果它发生了,它可能不会发生,正如另一个答案指出的那样。)也就是说,你不能依赖在控制返回之前执行的终结器线程Collect

这是它如何工作的过度简化的草图:

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

正如我所说,这过于简单化了。终结器队列如何工作的确切细节比这要复杂一些。但它已经足够传达这个想法了。这里的实际结果是你不能假设调用Collect也运行 finalizers,因为它没有。让我再重复一遍:垃圾收集器的跟踪部分运行终结器,而Collect只运行收集机制的跟踪部分。

如果您想保证所有终结器都已运行,请WaitForPendingFinalizers在调用后调用恰当命名的方法。Collect这将暂停当前线程,直到终结器线程开始清空队列。而且,如果您想确保这些最终对象的内存被回收,那么您将不得不再次Collect调用。

当然,不言而喻,您应该只出于调试和测试目的而这样做。千万不要在没有非常非常好的理由的情况下在生产代码中做这种废话。

于 2012-12-19T15:03:45.163 回答
15

实际上答案是“这取决于”。实际上有一个专用线程执行所有终结器。这意味着调用GC.Collect只触发了这个过程,所有终结器的执行都将被异步调用。

如果您想等到所有终结器都被调用,您可以使用以下技巧:

GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
于 2012-12-19T14:56:25.697 回答
10

是的,但不是马上。此摘录来自垃圾收集:Microsoft .NET Framework 中的自动内存管理(MSDN 杂志) (*)

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

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

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

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

于 2012-12-19T14:55:22.833 回答
5

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

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


顺便说一句,不能保证最终会被调用MSDN ...

在以下异常情况下,Finalize 方法可能不会运行完成或根本不会运行:

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

只有在可终结对象的数量继续减少时,运行时才会在关闭期间继续终结对象。

于 2012-12-19T14:57:58.940 回答
0

这里还有几点值得说明。

终结器是 .net 对象可以释放非托管资源的最后一点。仅当您未正确处置实例时才执行终结器。理想情况下,终结器在许多情况下都不应该被执行。因为正确的 dispose 实现应该抑制 finalization

这是正确 IDispoable Implementation 的示例

如果您调用任何一次性对象的 Dispose 方法,它应该清除所有引用并抑制终结。如果有任何不太好的开发人员忘记调用 Dispose 方法,Finalizer 就是救命稻草。

于 2016-01-31T22:54:14.273 回答