好的,众所周知,当GC将对象标识为垃圾时,它会隐式调用对象上的Finalize
方法。但如果我做GC.Collect()
会怎样?终结者还在执行吗?也许是一个愚蠢的问题,但是有人问我这个,我回答“是”,然后我想:“这完全正确吗?”
答案 0 :(得分:75)
好的,众所周知,当GC将对象标识为垃圾时,GC会隐式调用对象的Finalize方法。
不不不。这不是已知的,因为为了知识,语句必须是 true 。该声明是 false 。 垃圾收集器不会跟踪的终结器,无论是自行运行还是调用Collect
。 终结器线程在跟踪收集器找到垃圾后运行终结器,并且对Collect
的调用发生异步。 (如果它发生了,它可能不会,正如另一个答案所指出的那样。)也就是说,在控制从Collect
返回之前,你不能依赖终结器线程执行。
这是一个过于简单的草图,说明它是如何工作的:
正如我所说的那样,过于简单;终结器队列如何工作的确切细节比这复杂一点。但它足以解决这个问题。这里的实际结果是你不能假设调用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()
,否则终结器可能会在垃圾收集完成后很长时间内在后台继续执行。
Finalize方法可能无法运行完成或可能无法运行 所有在以下特殊情况下:
- 另一个终结器无限期地阻塞(进入无限循环,尝试获得它永远无法获得的锁等等)。因为 运行时尝试运行终结器以完成其他终结器 如果终结器无限期阻塞,则可能无法调用。
- 该过程终止,而不给运行时提供清理的机会。在这种情况下,运行时的第一个进程通知 终止是DLL_PROCESS_DETACH通知。
运行时仅在关闭期间继续完成对象的Finalize 可终结对象的数量继续减少。
答案 3 :(得分:0)
这里有几点值得说明。
Finalizer是.net对象可以释放非托管资源的最后一点。 仅当您未正确处理实例时才会执行终结器。 理想情况下,终结器绝不应该在很多情况下执行。因为正确的部署实现应该suppress the finalization。
Here is an example for correct IDispoable Implementation
如果您调用任何一次性对象的Dispose方法,它应该清除所有引用并禁止最终确定。如果有任何不太好的开发人员忘记调用Dispose方法,那么Finalizer就是救生员。