更新(2009年12月1日):
我想修改这个答案并承认原始答案存在缺陷。
原始分析确实适用于需要终结的对象,而且仍然存在一点:不应该只凭表面接受做法,而是需要准确深入地理解。
然而,事实证明,数据集、数据视图、数据表在构造函数中抑制了终结 - 这就是为什么显式调用Dispose()对它们无效的原因。
据推测,这是因为它们没有非托管资源;因此,尽管MarshalByValueComponent允许非托管资源,但这些特定的实现没有这个需求,可以放弃终止。
(.NET的作者会特别抑制终结在通常占用最多内存的类型上,说明这种练习在最终化类型方面的重要性。)
尽管如此,自.NET Framework问世以来(将近8年),这些细节仍然未得到充分记录,这是相当令人惊讶的(你基本上要靠自己去筛选矛盾、模棱两可的材料来整合框架的完整理解,在某些时候会感到沮丧,但确实提供了更全面的框架理解)。
经过大量阅读,我现在理解如下:
如果一个对象需要终结,则它可能会占用比必要时间更长的内存 - 原因如下:a)任何定义了析构函数(或从定义了析构函数的类型继承的类型)的类型都被视为可终结;b)在分配时(在构造函数运行之前),将指针放置在终结队列上;c)通常需要2次收集才能回收最终化对象(而不是标准1次);d)抑制终结并不会从终结队列中移除对象(根据!FinalizeQueue报告)此命令是误导性的;仅知道哪些对象在终结队列上(本身)并没有帮助;只有知道哪些对象在终结队列上且仍然需要终结才有帮助(是否有这样的命令?)
抑制终结会关闭对象头中的一位,向运行时表明它不需要调用它的终结器(不需要移动FReachable队列);它仍然在终结队列上(并继续由!FinalizeQueue在SOS中报告)。
DataTable、DataSet、DataView类都以MarshalByValueComponent为根,这是一个可以(潜在地)处理非托管资源的最终化对象。
- 因为DataTable、DataSet和DataView不会引入非托管资源,所以它们在构造函数中抑制了终结
- 虽然这是一种不寻常的模式,但它使调用者无需担心在使用后调用Dispose
- 这也意味着这些对象将出现在!FinalizeQueue下
- 这个特性以及DataTable可能会被共享到不同的DataSets中,很可能是DataSets不关心释放子DataTable的原因
- 然而,这些对象仍然应该在单次垃圾回收后可以被回收,就像它们的非终结对应物一样
4 (新参考文献):
原始回答:
这里有很多误导性的和通常非常差劣的答案 - 任何来到这里的人都应该忽略噪音,仔细阅读下面的参考文献。
毫无疑问,任何可终结对象Dispose后都应该被调用。
DataTables是可终结的。
调用Dispose显著地加速了内存回收。
MarshalByValueComponent在其Dispose()中调用了< strong>GC.SuppressFinalize(this) - 跳过此步骤意味着需要等待数十甚至数百个Gen0集合才能回收内存:
有了对finalization的基本理解,我们就可以推断出一些非常重要的事情:
首先,需要finalization的对象的生命周期比不需要finalization的对象长。实际上,它们可以活得更久。例如,假设一个在gen2中的对象需要finalization。finalization将被安排,但是对象仍然在gen2中,因此直到下一次gen2收集发生时,它才不会被重新收集。这可能需要很长时间,如果一切顺利,它将需要很长时间,因为gen2收集成本很高,因此我们希望它们非常少发生。需要finalization的旧对象可能需要等待几十甚至数百次gen0收集,才能回收它们的空间。
其次,需要finalization的对象会造成副作用。由于内部对象指针必须保持有效,不仅需要直接需要finalization的对象留存在内存中,而且所有对象直接或间接引用的东西也将留在内存中。如果一个巨大的对象树由需要finalization的单个对象锚定,那么整个树都将滞留,可能需要很长时间,就像我们刚才讨论的那样。因此,使用finalizers时要谨慎,并尽可能将其放置在具有尽可能少的内部对象指针的对象上。在我刚才给出的树的例子中,您可以通过将需要finalization的资源移动到单独的对象中并在树的根部保留对该对象的引用来轻松避免问题。通过这个简单的更改,只有一个对象(希望是一个很小的对象)会滞留,并且最小化了finalization的成本。
最后,需要finalization的对象会为finalizer线程创建工作。如果您的finalization过程很复杂,唯一的finalizer线程将花费大量时间执行这些步骤,这可能会导致工作积压,因此会导致更多的对象等待finalization而滞留。因此,非常重要的是,finalizers尽可能少地进行工作。还要记住,虽然在finalization期间所有对象指针仍然有效,但可能存在这样一种情况,即这些指针引导到已经完成finalization的对象,因此可能没有用处。通常最安全的做法是避免在finalization代码中跟随对象指针,即使这些指针是有效的。安全、简短的finalization代码路径是最好的。
从看到Gen2中数百MB未被引用的DataTable的人那里得知:这非常重要,而且这个主题上的答案完全忽略了这一点。
参考资料:
1 -
http://msdn.microsoft.com/zh-cn/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/chinese/csharp/net-framework/内部组件和CLR/Automatic-Memory-Management/
try { some.Dispose(); } catch {}
足矣。 - LukeSwDispose
最坏的情况下很可能只是一个无操作。 - user7116