因此,默认的dispose模式实现如下所示:
class SomeClass : IDisposable
{
// Flag: Has Dispose already been called?
bool disposed = false;
// Public implementation of Dispose pattern callable by consumers.
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing) {
// Free any other managed objects here.
}
// Free any unmanaged objects here.
disposed = true;
}
~SomeClass()
{
Dispose(false);
}
}
据说:
如果方法调用来自终结器(即,如果处理是
false
),则只执行释放非托管资源的代码。由于未定义垃圾收集器在最终确定期间销毁托管对象的顺序,因此使用值Dispose
调用此false
重载会阻止终结器尝试释放可能已经回收的托管资源。
问题是:为什么假设SomeClass
对象引用的对象可能已经被释放,当从终结器调用方法时我们不应该尝试处理它们?如果这些对象仍被我们的SomeClass
对象引用,则无法释放它们,是不是真的?据说:
具有挂起(未运行)终结器的那些将保持活动状态(暂时)并被置于特殊队列中。 [...]在每个对象的终结器运行之前,它仍然非常活跃 - 该队列充当根对象。
因此,我们的SomeClass
对象再次由此队列引用(与根引用相同)。 SomeClass
对象引用的其他对象也应该是活着的(因为它们是通过SomeClass
对象生根的)。那么为什么以及如何在调用SomeClass
终结器时释放它们?
Konrad Kokosa对他的书Pro .NET Memory Management有一个令人印象深刻的解释。 (重点补充)
在GC期间,在Mark阶段结束时,GC会检查终结队列以查看是否有任何可终结对象已死亡。如果它们是某些,它们还不能删除,因为它们的终结器需要执行。因此,此类对象被移动到另一个名为fReachable queue的队列。它的名字来自于它表示终结可到达对象的事实 - 现在只能由于最终化而可以到达的对象。如果找到任何此类对象,GC会向专用的终结器线程指示有工作要做。
终结线程是.NET运行时创建的另一个线程。它逐个从fReachable队列中删除对象并调用它们的终结器。 GC恢复托管线程后会发生这种情况,因为终结器代码可能需要分配对象。由于此对象的唯一根目录已从fReachable队列中删除,因此下一个谴责此对象生成的GC将发现它无法访问并回收它。
此外,fReachable队列被视为在Mark阶段考虑的根,因为终结器线程可能不够快,无法在GC之间处理它的所有对象。这使得可终结的对象更多地暴露于中年危机 - 他们可能因为待定的最终化而停留在fReachable队列中一段时间而消耗第二代。
我认为这里的关键是:
fReachable队列被视为在Mark阶段考虑的根,因为终结器线程可能不够快,无法在GC之间处理它的所有对象。
.NET中的对象存在,而对它们存在任何引用。一旦最后一个参考文献这样做,它们就不再存在。当对象存在时,对象使用的存储将永远不会被回收,但GC在回收存储之前会做一些事情:
请注意,finalize方法不会“垃圾收集”对象。相反,它会延长对象的存在,直到finalize
被调用它为目的,以允许它满足它可能对外部实体的任何义务。如果此时在Universe中的任何位置都不存在对象的引用,则该对象将不再存在。
请注意,具有终结器的两个对象可以相互保持引用。在这种情况下,其终结者的运行顺序是未指定的。