C#语言:垃圾收集,SuppressFinalize

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

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

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

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

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

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

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

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

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

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

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

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

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

要解决此问题,请使用终结器。 这是如何运作的?

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

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

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

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

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

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

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

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

这些物体什么时候会被破坏?

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

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

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

结束!=毁灭

.NET中不存在析构函数(在C ++意义上) – 因为在某种意义上, 每个对象都有一个“析构函数”,称为垃圾收集器。 🙂
C#所谓的“析构函数”实际上是终结符。 终结器用于处理除对象分配的内存之外的东西,例如文件句柄等。因此,并非每个对象都有终结器。 内存总是由GC释放,因此您不会以这种方式泄漏内存。

我只知道第二个答案: 当对象已被破坏时,即通过IDisposable.Dispose来调用SuppressFinalize 。 所以它不应该再被破坏。

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

编辑:在进程终止时,内存泄漏并不重要。 当一个进程终止时,它的堆由Windows收集,因此对象的内存是否返回到堆并不重要。

我试着回答Q2:

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

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

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

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