C#语言:垃圾收集,SuppressFinalize

时间:2011-07-11 14:53:02

标签: c# .net garbage-collection suppressfinalize

我正在阅读“C#语言”,第4版,它谈论垃圾收集如下:

  

“BILL WAGNER:以下规则是C#与其他托管环境之间的重要区别。

     

在应用程序终止之前,会调用所有尚未被垃圾回收的对象的析构函数,除非已经抑制了这种清理(例如,通过调用库方法GC.SuppressFinalize)。“ p>

所以我在这里有几个问题:

  • Q1。为什么.net与其他托管环境不同(我想这是暗示Java?)?任何特定的设计问题?

  • Q2。调用GC.SuppressFinalize的对象会发生什么? 我明白这意味着GC不会调用这些对象的终结器(析构函数),如果是这样,这些对象什么时候才会被破坏,以便分配的内存位返回堆?否则会有内存泄漏?

5 个答案:

答案 0 :(得分:39)

  

调用GC.SuppressFinalize的对象会发生什么?我明白这意味着GC不会调用这些对象的终结器(析构函数),如果是这样,这些对象什么时候才会被破坏呢?否则会有内存泄漏吗?

您对最终确定的内容存在误解。最终确定用于清理托管内存的资源。

假设您有一个包含整数字段的引用类型的对象。该整数字段恰好是通过调用非托管代码打开文件而获得的文件的句柄。

由于某些其他程序可能想要访问该文件,因此请尽快关闭该文件。但.NET运行时不知道这个整数对操作系统有任何特殊含义。它只是一个整数。

解决此问题的方法通常是将对象标记为实现IDisposable,然后在完成后立即调用对象上的“Dispose”。然后,您的“Dispose”实现将关闭文件。

请注意,此处没有什么特别之处。清理非托管资源的方法称为“Dispose”,而需要处理的对象实现IDisposable只是一种约定。垃圾收集对此一无所知。

所以现在问题出现了:如果有人忘记调用Dispose怎么办?文件是否永远打开 ? (显然,当流程结束时文件将被关闭,但如果流程运行很长时间会怎么样?)

要解决此问题,请使用终结器。这有什么作用?

当一个对象即将被垃圾收集时,垃圾收集器会检查它是否有一个终结器。如果是,那么它将其放在终结器队列上,而不是垃圾收集它。在未来的某个未指定的点上,运行一个线程来检查队列并在每个对象上调用一个特殊的“Finalize”方法。之后,该对象将从终结队列中删除并标记为“嘿,我已经完成了”。该对象现在再次可以进行收集,因此垃圾收集器最终运行并收集对象,而不用将其放在终结队列中。

显然,“终结”和“处理”经常需要做同样的事情。

但是现在出现了另一个问题。假设您处置了一个对象。 现在它不需要最终确定。终结费用昂贵;它使一个死对象活着的时间比它需要的时间长得多。因此,传统上当一个人处理一个对象时,Dispose的实现不仅会关闭非托管资源,还会将对象标记为“此对象已经完成,不再对其进行最终确定”。这样就可以欺骗垃圾收集器,而不是将对象放在终结队列中。

让我们回答您的具体问题:

  

调用GC.SuppressFinalize的对象会发生什么?

当对象死了时,垃圾收集器将简单地回收对象的内存而不将对象放在终结器队列上。

  

我知道这意味着GC不会调用此类对象的终结器

GC 从不调用终结器。终结器线程是唯一调用终结器的东西。

  

这些物体何时会被破坏?

目前尚不清楚“被毁”是什么意思。如果你的意思是“终结者什么时候会运行?”答案是“从不”,因为你说压制定稿。如果您的意思是“何时回收托管堆中的内存?”,答案是“一旦垃圾收集器将对象识别为死亡”。这将比正常情况更早发生,因为终结器队列不会保持对象的存活。

答案 1 :(得分:5)

第一季度:我怀疑这是因为它更关心如何实现卓越的性能,而不是Java为简单性做出了不少牺牲。

Q2:由于终结器甚至不能保证首先被调用(即使SuppressFinalize不存在),这只应该用于性能原因,当你已经处理完资源时。否则,您应该使用IDisposable来处置资源。

敲定!=毁灭

.NET中不存在析构函数(在C ++意义上) - 因为每个对象在某种意义上都有一个“析构函数”,称为垃圾收集器。 :)
C#称之为“析构函数”的实际上是终结符。 终结器用于处理 other 而不是对象分配的内存,例如文件句柄等。因此,并非每个对象都有终结器。 GC始终释放内存,因此不会出现内存泄漏。

答案 2 :(得分:2)

我只知道第二个答案:SuppressFinalize is called when the object has already been destructed, i.e., by IDisposable.Dispose.所以不应该再次被破坏。

这是因为终结器是在非确定性时间发生的资源清理。添加了IDisposable以允许在特定的可预测时间内进行资源清理。

编辑:在进程终止时,内存泄漏并不重要。当一个进程终止时,它的堆被Windows收集,所以无论对象的内存是否返回堆都没关系。

答案 3 :(得分:1)

我尝试回答Q2:

如果你的类有一个终结器(由~ClassName显示),那么垃圾收集不会直接收集它,而是将它放在终结器队列中,稍后将调用它。现在,当最终调用终结器时,它通常会释放任何非托管资源,即创建的类。如果由于任何原因用户已经这样做,通常通过调用dispose,终结器不需要清理非托管内存,因此调用SuppressFinalizer是必要的。否则它会尝试再次释放资源。 因此,您将获得的唯一内存泄漏是您在创建类时请求的任何资源。终结器只是为了释放框架尚未管理的所有内容。

答案 4 :(得分:0)

因为曾经调用GC.Collect() 具有finalize的对象将至少移动到下一代。

我们可以调用GC.SuppressFinalize,而GC会知道这个对象是de-reference,我们可以删除这个对象然后压缩堆。

通常,这个工具具有配置模式