GC.Collect()和Finalize

San*_*eep 45 .net c# garbage-collection finalizer

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

Eri*_*ert 75

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

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

这是一个过于简单的草图,说明它是如何工作的:

  • 当集合发生时,垃圾收集器跟踪线程跟踪根 - 已知的对象,以及它们引用的每个对象,等等 - 来确定死对象.
  • 具有挂起的终结器的"死"对象被移动到终结器队列上.终结器队列是根.因此,那些"死"的物体实际上仍然存在.
  • 终结器线程(通常是与GC跟踪线程不同的线程)最终运行并清空终结器队列.那些对象然后变得真正死了,并在跟踪线程的下一个集合中收集.(当然,因为他们刚刚在第一次收藏中幸存下来,他们可能会在更高的一代.)

正如我所说的那样,过于简单; 终结器队列如何工作的确切细节比这复杂一点.但它足以解决这个问题.这里的实际结果是你不能假设调用Collect也运行终结器,因为它没有.让我再说一遍:垃圾收集器的跟踪部分运行终结器,Collect只运行收集机制的跟踪部分.

如果要保证所有终结器都已运行,请WaitForPendingFinalizers在调用后调用恰当命名Collect.这将暂停当前线程,直到终结器线程到处清空队列.如果你想确保那些最终确定对象有自己的内存回收,然后你将不得不叫Collect一个第二时间.

当然,不言而喻,您只应该为调试和测试目的而这样做.如果没有真正的理由,切勿在生产代码中做这些废话.

  • 很高兴看到你回到SO,Eric. (3认同)
  • @MgSam:谢谢!很高兴有一些空闲时间花在它上面!我们会看到它持续多久. (2认同)

Ser*_*kov 15

实际上答案"这取决于".实际上有一个专用线程来执行所有终结器.这意味着GC.Collect只调用触发此过程并执行所有终结器将异步调用.

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

GC.Collect();
// Waiting till finilizer thread will call all finalizers
GC.WaitForPendingFinalizers();
Run Code Online (Sandbox Code Playgroud)


stu*_*rtd 10

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

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

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

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

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


Bra*_*vic 5

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

除非你打电话GC.WaitForPendingFinalizers(),否则终结器可能会在垃圾收集完成后很长时间内继续在后台执行.


BTW,也不能保证终结将被称为在所有.来自MSDN ......

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

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

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