好的,众所周知,当GC将对象标识为垃圾时,它会隐式调用对象的Finalize
方法。但是如果我执行GC.Collect()
会发生什么?最终器依然会被执行吗?有人问了我这个问题,我回答了“是的”,然后我想:“那是否完全正确呢?”
好的,众所周知,当GC将对象标识为垃圾时,它会隐式调用对象的Finalize
方法。但是如果我执行GC.Collect()
会发生什么?最终器依然会被执行吗?有人问了我这个问题,我回答了“是的”,然后我想:“那是否完全正确呢?”
就像我说的那样,这有些过于简化了;最终器队列的详细工作方式比这要复杂一些。但它足以传达足够的思想。在这里的实际影响是,你不能假定调用Collect
也会运行终结器,因为它不会。让我再重复一遍:垃圾收集器的跟踪部分 不会 运行终结器 ,而Collect
仅运行收集机制的跟踪部分。
如果您希望保证所有终结器已运行,请在调用Collect
后调用名副其实的WaitForPendingFinalizers
。它将暂停当前线程,直到终结器线程完成清空队列。如果您想确保那些已终结的对象已被回收,则需要再次调用Collect
第二次。
当然,毋庸置疑,您应该只将此用于调试和测试目的。在没有非常,非常好的理由的情况下,不要在生产代码中执行此类操作。
实际上答案是"这取决于情况"。实际上有一个专门的线程执行所有终结器。这意味着对 GC.Collect
的调用仅触发了此过程,并且执行所有终结器将异步调用。
如果您想等待直到所有终结器都被调用,您可以使用以下技巧:
GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
IDisposable.Dispose
(最好通过 using 语句)来代替。 - Brian可以,但不是立即进行。这段摘录来自Microsoft .NET Framework的垃圾回收:自动内存管理 (MSDN Magazine) (*)
当应用程序创建一个新对象时,new运算符从堆中分配内存。如果对象的类型包含Finalize方法,则会将指向该对象的指针放置在终结器队列上。终结器队列是由垃圾回收器控制的内部数据结构。队列中的每个条目都指向一个对象,该对象在其内存可以被回收之前应调用其Finalize方法。
当发生GC时,垃圾回收器会扫描终结器队列,寻找指向这些对象的指针。找到指针后,指针将从终结器队列中删除,并附加到可及队列(发音为“F-可达”)。可及队列是另一个由垃圾回收器控制的内部数据结构。可及队列中的每个指针标识一个准备调用其Finalize方法的对象。
有一个专用于调用Finalize方法的特殊运行时线程。当可及队列为空(通常情况下是这样)时,此线程会进入休眠状态。但是,当条目出现时,此线程会醒来,从队列中删除每个条目,并调用每个对象的Finalize方法。因此,您不应在Finalize方法中执行任何假设执行代码的线程的代码。例如,在Finalize方法中避免访问线程本地存储。
(*) 来自2000年11月,所以有些内容可能已经发生变化。
GC.Collect()
)进行垃圾回收时,需要终结的对象会被放入终结队列。GC.WaitForPendingFinalizers()
,否则这些对象的终结器可能会在垃圾回收完成后长时间在后台执行。以下情况下,终结器可能无法运行到完成或根本不运行:
- 另一个终结器无限期地阻塞(进入无限循环、尝试获取它永远无法获取的锁等等)。因为运行时试图将终结器运行到完成,所以如果一个终结器无限期地阻塞,则可能不会调用其他终结器。
- 进程在未给运行时清理的机会的情况下终止。在这种情况下,运行时对进程终止的第一个通知是 DLL_PROCESS_DETACH 通知。
仅当可终结对象数量持续减少时,运行时才会在关闭期间继续终结对象。
这里还有几点值得提一下。
Finalizer 是 .net 对象释放非托管资源的最后一步。只有在您没有正确处理实例时,才会执行 Finalizers。理想情况下,在许多情况下都不应该执行 finalizers,因为适当的 dispose 实现应该抑制终结操作。
如果您调用任何可处置对象的 Dispose 方法,则应清除所有引用并抑制终结操作。如果有任何不太好的开发人员忘记调用 Dispose 方法,则 Finalizer 就是救命稻草。