16

我正在阅读“The C# Language”,第 4 版,它谈到了垃圾收集,如下所示:

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

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

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

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

  • Q2。GC.SuppressFinalize被调用的对象会发生什么?我知道这意味着GC不会调用这些对象的终结器(析构函数),如果是这样,这些对象什么时候会真正被销毁,以便将分配的内存位返回到堆中?否则会出现内存泄漏?

4

5 回答 5

40

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

您对最终确定的用途有误解。终结是为了清理托管内存的资源。

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

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

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

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

那么现在问题来了:如果有人忘记调用 Dispose 怎么办?文件是否永远保持打开状态?(明明进程结束时文件会被关闭,但是如果进程运行了很长时间呢?)

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

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

显然,“Finalize”和“Dispose”经常需要做同样的事情。

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

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

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

当对象死亡时,垃圾收集器将简单地回收对象的内存,而不会将对象放入终结器队列中。

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

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

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

目前尚不清楚您所说的“破坏”是什么意思。如果您的意思是“终结器何时运行?” 答案是“从不”,因为您说要禁止最终确定。如果您的意思是“托管堆中的内存何时被回收?”,答案是“一旦对象被垃圾收集器识别为死”。这将比正常情况更早发生,因为终结器队列不会使对象保持活动状态。

于 2011-07-11T15:11:59.630 回答
5

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

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

最终确定!= 破坏

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

于 2011-07-11T15:01:59.950 回答
2

我只知道第二个答案:SuppressFinalize在对象已经被破坏时调用,即 by IDisposable.Dispose所以它应该再次被破坏。

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

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

于 2011-07-11T14:55:40.717 回答
1

我尝试回答 Q2:

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

于 2011-07-11T14:58:43.727 回答
0

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

我们可以调用 GC.SuppressFinalize,GC 会知道这个对象被取消引用,然后我们可以删除这个对象并压缩堆。

通常,该工具具有处置模式

于 2018-01-10T09:19:43.937 回答