【问题标题】:GC.Collect() and FinalizeGC.Collect() 和完成
【发布时间】:2012-12-06 22:47:16
【问题描述】:

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

【问题讨论】:

  • 为什么你没有尝试通过登录析构函数来查看是否调用了“Finalize”?还是我错过了什么?!
  • 这当然不是一个“愚蠢的问题”。
  • @Likurg - 是的,我本可以这样做并避免发布问题。但是,知识将仅限于我。好的部分是 - 通过在这里发布,我得到了关于该主题的一些更详细的答案,我希望它也可以帮助其他 SO 用户!

标签: c# .net garbage-collection finalizer


【解决方案1】:

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

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

这是一个过于简化的工作原理示意图:

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

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

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

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

【讨论】:

  • @Sandeep:嗯,我想这取决于你是否认为终结器线程是垃圾收集器的一部分。我认为垃圾收集器是专门追踪根源的东西,但如果你认为垃圾收集器是所有处理清理的机制,那么我想这是真的。我会澄清答案。
  • @Sandeep:要寻找的重要事实不是我们所说的“垃圾收集器”,而是您最初问题的答案:我们如何保证终结器已经运行?您可以通过等待终结器线程完成其工作来保证这一点。
  • 我完全同意这一点。但我只是想知道(不好的好奇心)——谁通知终结器线程开始执行?是GC吗?
  • 很高兴看到你回来,埃里克。
  • @MgSam:谢谢!很高兴有一些空闲时间花在上面!我们会看看它会持续多久。
【解决方案2】:

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

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

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

【讨论】:

  • @OP:您可能想要这样做是有原因的(例如,作为性能优化)。但是,如果使用此类代码的动机是修复程序逻辑,则代码可能应该使用 IDisposable.Dispose(最好是通过 using 语句)。
【解决方案3】:

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

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

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

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

(*) 从 2000 年 11 月开始,所以事情可能从那以后发生了变化。

【讨论】:

    【解决方案4】:

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

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


    顺便说一句,不能保证终结器会被调用。来自MSDN...

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

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

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

    【讨论】:

      【解决方案5】:

      还有几点值得在这里说明。

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

      Here is an example for correct IDispoable Implementation.

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

      【讨论】:

        猜你喜欢
        • 2012-05-11
        • 1970-01-01
        • 1970-01-01
        • 2019-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多