垃圾收集和终结器:更好的点

在回答关于SO的另一个问题*以及随后的评论讨论时,我遇到了一个我不清楚的问题。

在我误入歧途的任何地方纠正我……

当垃圾收集器收集一个对象时,它会在一个单独的线程上调用该对象的终结器(除非终结器已被抑制,例如通过Dispose()方法)。 在收集时,GC会挂起除触发收集的线程(除了背景集合)之外的所有线程。

不清楚的是:

  1. 在收集垃圾收集器之前,垃圾收集器是否等待终结器在该对象上执行?
  2. 如果没有,它是否在终结器仍在执行时取消挂起线程?
  3. 如果它确实等待,如果终结器遇到其中一个被挂起的线程所持有的锁,会发生什么? 终结器线程是否会死锁? (在我的回答中,我认为这是糟糕的设计,但我可能会看到可能发生这种情况的情况)

*链接到原始问题:
.NET GC从终结器访问同步对象

在收集垃圾收集器之前,垃圾收集器是否等待终结器在该对象上执行?

你的问题有点含糊不清。

当GC遇到需要完成的“死”对象时,它放弃了回收死对象存储的尝试。 相反,它将对象放在“我知道需要完成的对象”的队列中, 并将该对象视为活动,直到终结器线程完成它为止。

所以,是的,GC确实“等待”,直到在回收存储之前执行终结器。 但它不会同步等待。 听起来你问“GC是否同步调用终结器?” 不,它将对象排队等待稍后完成并继续卡车运行。 GC希望快速完成释放垃圾和压缩内存的任务,以便程序能够尽快恢复运行。 在它被清理之前,它不会停止处理一些需要注意的怪物。 它将该对象放在队列中并说“保持安静,终结器线程将在稍后处理你”。

之后GC会再次检查对象并说“你还在死吗?还有你的终结器吗?” 如果答案为“是”,则对象将被回收。 (请记住,终结者可能会将一个死对象重新变为现实对象;尽量不要这样做。结果没有任何令人愉快的事情发生。)

在终结器仍在执行时是否取消挂起线程?

我相信GC解冻了它冻结的线程,并向终结器线程发出信号“嘿,你有工作要做”。 因此,当终结器线程开始运行时,GC冻结的线程将再次启动。

可能必须存在未冻结的线程,因为终结器可能需要将调用编组到用户线程以释放线程关联资源。 当然,其中一些用户线程可能被阻止或冻结; 线程总是被某些东西阻挡。

如果终结器遇到其中一个被挂起的线程所持有的锁,会发生什么? 终结器线程是否会死锁?

完全正确。 终结器线程没有什么神奇之处可以阻止它死锁。 如果用户线程正在等待终结器线程取出的锁定,并且终结器线程正在等待用户线程取出的锁定,那么您就会遇到死锁。

终结器线程死锁的例子比比皆是。 这是一篇关于其中一个场景的好文章,其中包含一系列指向其他场景的链接:

http://blogs.microsoft.co.il/blogs/sasha/archive/2010/06/30/sta-objects-and-the-finalizer-thread-tale-of-a-deadlock.aspx

正如文章所述: 终结器是一种极其复杂和危险的清理机制,如果可能的话你应该避免它们 。 让终结器出错是非常容易的,并且很难将其弄好。

包含终结器的对象往往寿命更长。 在收集期间,当GC将终结器标记为垃圾时,它将不会收集该对象(尚未)。 GC会将该对象添加到将 GC完成运行的终结器队列中。 这样做的后果是,因为没有收集这个对象,所以它会移动到下一代(以及它所引用的所有对象)。

GC挂起所有正在运行的线程。 另一方面,终结器线程将在应用程序继续运行时在后台运行。 终结器调用所有注册用于终结的对象的所有finalize方法。 在对象的终结器方法运行之后,该对象将从队列中删除,并且从对象上的那个点(可能还有它仍然引用的所有对象)都是垃圾。 清除该对象生成对象的下一个集合将(最后)删除该对象。 由于生成在第2代中的对象被收集的速度大约是生成第1代的对象的10倍,而第1代收集的速度是第0代的10倍,因此最终垃圾收集可能需要一些时间。

因为终结器线程只是一个运行托管代码的简单线程(它调用终结器),所以它可以阻塞甚至死锁。 因此,在finalize方法中尽可能少地做很重要。 因为终结器是后台线程,失败的终结方法甚至可以降低完整的AppDomain(哎呀!)。

你可以说这个设计是不幸的,但如果你考虑一下,其他设计框架有效地清理我们的混乱,很难想象。

那么,回答你的问题:

  1. 是的,只有在从终结器队列中删除对象之后,该对象才是垃圾,GC将收集它。
  2. GC挂起所有线程,甚至是终结器队列。
  3. 终结器队列可以死锁。 在finalize方法中尽可能少地锁定。

将垃圾收集器视为将对象分为四组是最简单的:

  1. 任何有根对象无法访问的对象;
  2. 可从可实现的可终结对象列表中访问的对象,但不能从任何其他有根对象访问;
  3. 那些在可实现的可终结对象列表中,但也可以通过除该列表之外的某个有根对象访问。
  4. 那些不在可实现的可终结对象列表中的,但可以通过除该列表之外的某个有根对象访问。

当垃圾收集器运行时,#1类型的对象消失。 #2的对象被添加到需要即将完成的对象列表中,并从“实时可终结对象”列表中删除(从而成为类别#4的对象)。 请注意,需要最终化的对象列表是普通的有根引用,因此当列表上的对象无法收集时,如果在终结器完成时没有创建其他有根引用,则对象将移至类别# 1。