GC是否需要多次收集具有挂起终结器的对象



我正在阅读"处理和垃圾收集";在一个Nutshell中的C#8.0的章节。当谈到终结器时,它说:

GC识别要删除的未使用对象终结器被立即删除,那些具有挂起的终结器的保持生命并被放入一个特殊的队列中。当垃圾集合完成并且程序继续执行时终结器线程然后开始与程序并行运行,从该特殊队列中拾取对象并运行它们的终结方法。

这段话是否意味着GC需要再次收集等待完成的对象?我假设GC已经检测到它是垃圾,为什么需要在完成后再次收集它?

嗯,对象不是第一次"收集"的。他们被认为需要额外的处理(需要运行终结器代码(,并被放在终结队列中,以便可以单独处理。这最终将它们放在"freachable"队列中,该队列现在已经恢复了对象:它现在被freachable队列引用,不再符合收集条件。在终结器实际执行并且对象从可刷新队列中删除之后,它将不可访问。

(这就是它过去的工作方式,不确定在较新的.NET版本中是否发生了变化,但我不知道有任何变化。(

因此,如果通过"collected"我们了解到内存是回收的,那么对象实际上并没有被"收集"多次。然而,它确实需要额外的处理,GC将在稍后的时间点再次对其进行重新评估。

GC通过从GC根遍历对象图来工作。当GC进行收集时,它会检查没有引用的对象(因此可以安全释放(。

终结器延迟对象的垃圾收集。

为什么?GC认为对象可以安全地释放(不连接到GC根(。然而,如果有一个终结器尚未运行,它就无法释放内存。

因此,GC将对象标记为具有挂起的终结器,并且在第一次通过时不会释放该空间。GC也不会在那一刻运行终结器(它将其放入"挂起终结器"队列(。

这正是为什么除非必要,否则使用终结器是一种糟糕的做法。它延迟了收集。有些人有一种误解,认为GC在集合传递时运行终结器。事实并非如此。

什么时候有必要?一个好的经验法则是,如果对象引用非托管内存(GC不处理(,那么您绝对应该使用终结器来避免内存泄漏。如果您只引用托管对象,那么不要引用。

如果您确实实现了终结器,我也将实现IDisposable,释放Dispose上的任何非托管资源,并停止终结器与GC.SuppressFinalize(this)一起运行。

最新更新