更新(2009 年 12 月 1 日):
我想修改这个答案并承认原来的答案是有缺陷的。
最初的分析确实适用于需要最终确定的对象——没有准确、深入的理解,不应该在表面上接受实践的观点仍然存在。
然而,事实证明 DataSets、DataViews、DataTables在它们的构造函数中抑制了终结——这就是为什么在它们上显式调用 Dispose() 什么都不做的原因。
据推测,这是因为它们没有非托管资源。因此,尽管MarshalByValueComponent允许使用非托管资源,但这些特定的实现不需要,因此可以放弃最终确定。
(.NET 作者会注意抑制通常占用最多内存的类型的终结性,这说明了这种做法对于可终结类型的重要性。)
尽管如此,自从 .NET Framework (大约 8 年前)问世以来,这些细节仍然没有得到充分的记录,这非常令人惊讶(您基本上只能依靠自己的设备来筛选相互冲突、模棱两可的材料来将各个部分组合在一起有时令人沮丧,但确实提供了对我们每天所依赖的框架的更完整的理解)。
经过大量阅读,这是我的理解:
如果一个对象需要终结,它占用的内存可能比它需要的时间长——原因如下: a) 任何定义析构函数(或从定义析构函数的类型继承)的类型都被认为是可终结的;b) 在分配时(在构造函数运行之前),一个指针被放置在终结队列上;c) 一个可终结的对象通常需要回收2 个集合(而不是标准的 1 个);d) 抑制终结不会从终结队列中删除对象(如 SOS 中的 !FinalizeQueue 所报告的) 此命令具有误导性;知道最终队列中的对象(本身)是没有帮助的;知道什么对象在终结队列上并且仍然需要终结将是有帮助的(有这个命令吗?)
抑制终结会在对象的标头中关闭一点,向运行时指示它不需要调用其终结器(不需要移动 FReachable 队列);它保留在 Finalization 队列中(并继续由 SOS 中的 !FinalizeQueue 报告)
DataTable、DataSet、DataView 类都植根于 MarshalByValueComponent,这是一个可以(可能)处理非托管资源的可终结对象
- 因为 DataTable、DataSet、DataView 不引入非托管资源,它们在构造函数中抑制了终结
- 虽然这是一种不寻常的模式,但它使调用者不必担心在使用后调用 Dispose
- 这一点,以及 DataTables 可能在不同的 DataSets 之间共享这一事实,可能是 DataSets 不关心处置子 DataTables 的原因
- 这也意味着这些对象会出现在 SOS 中的 !FinalizeQueue 下
- 但是,这些对象在单次收集后仍应可回收,就像它们的不可终结对象一样
4(新参考):
原答案:
对此有很多误导性且通常非常糟糕的答案 - 任何来到这里的人都应该忽略噪音并仔细阅读下面的参考资料。
毫无疑问,应该在任何 Finalizable 对象上调用 Dispose。
数据表是可终结的。
调用 Dispose显着加快了内存的回收。
MarshalByValueComponent在其 Dispose() 中调用GC.SuppressFinalize(this) - 跳过这意味着在回收内存之前必须等待数十个(如果不是数百个)Gen0 集合:
有了对 finalization 的基本理解,我们已经可以推断出一些非常重要的事情:
首先,需要终结的对象比不需要终结的对象寿命更长。事实上,他们可以活得更久。例如,假设需要完成 gen2 中的对象。将安排完成,但对象仍在 gen2 中,因此在下一次 gen2 收集发生之前不会重新收集它。这确实可能是一个很长的时间,事实上,如果事情进展顺利,这将是很长一段时间,因为 gen2 收集成本很高,因此我们希望它们很少发生。需要完成的旧对象可能必须等待数十个甚至数百个 gen0 集合才能回收它们的空间。
其次,需要最终确定的对象会造成附带损害。由于内部对象指针必须保持有效,不仅直接需要终结的对象会在内存中徘徊,而且对象直接和间接引用的所有内容也将保留在内存中。如果一棵巨大的对象树由一个需要最终确定的对象锚定,那么整个树将徘徊,可能会像我们刚刚讨论的那样持续很长时间。因此,谨慎使用终结器并将它们放置在具有尽可能少的内部对象指针的对象上是很重要的。在我刚刚给出的树示例中,您可以通过将需要完成的资源移动到单独的对象并在树的根中保留对该对象的引用来轻松避免该问题。
最后,需要终结的对象为终结器线程创建工作。如果您的终结过程是一个复杂的过程,那么唯一的终结器线程将花费大量时间执行这些步骤,这可能会导致工作积压,从而导致更多对象等待终结。因此,终结者尽可能少地做工作是至关重要的。还要记住,虽然所有对象指针在终结期间都保持有效,但这些指针可能会指向已经终结的对象,因此可能不太有用。即使指针是有效的,在终结代码中避免跟随对象指针通常是最安全的。安全、简短的最终确定代码路径是最好的。
从在 Gen2 中看到 100 MB 的非引用数据表的人那里得到它:这非常重要,并且完全被这个线程上的答案所忽略。
参考:
1 -
http://msdn.microsoft.com/en-us/library/ms973837.aspx
2 -
http://vineetgupta.spaces.live.com/blog/cns!8DE4BDC896BEE1AD!1104.entry
http://www.dotnetfunda.com/articles/article524-net-best-practice-no-2-improve-garbage -collector-performance-using-finalizedispose-pattern.aspx
3 -
http://codeidol.com/csharp/net-framework/Inside-the-CLR/Automatic-Memory-Management/