【问题标题】:Will ignoring IDisposable cause memory leaks?忽略 IDisposable 会导致内存泄漏吗?
【发布时间】:2011-07-28 20:35:53
【问题描述】:

answer I wrote 的 cmets 中,我们讨论了内存泄漏和IDisposable,但我们没有得出任何真正的结论。

处理非托管资源的类可能实现IDisposable。如果忽略这一点,既不调用Dispose,也不将对象包装在using 中——这会导致非托管资源泄露吗?还是在 GC 收集对象时会正确清理?

我们可以假设处理非托管资源的类具有IDisposable的正确实现,包括终结器等。

【问题讨论】:

  • 假设事情做得正确可能是一件危险的事情。但即便如此,这也是相关的:stackoverflow.com/questions/6652044/…
  • 在很多情况下,无法编写正确的终结器,因为 Finalize 是在不同的线程上调用的,而 Dispose 是在正常调用时调用的。
  • @AresAvatar: +1 事实上,我认为很遗憾微软没有声明所有正确实现的事件都必须提供线程安全的取消订阅方式,因为即使事件订阅者发现他是不再需要(例如,因为对事件感兴趣的实体持有强引用但事件处理程序没有最终确定的对象)他不能取消订阅该事件,除非他知道发布者提供线程安全的取消订阅。

标签: c# .net idisposable


【解决方案1】:

它不会导致内存泄漏。事实上,Dispose 与内存管理完全无关。

它将创建一个资源泄漏。虽然 GC 通常会清理它,但这可能太少且太晚了。

省略 Dispose (using) 可能会减慢甚至崩溃您的应用程序。在文件资源或数据库连接的情况下,它甚至可能导致其他应用程序出现问题。

【讨论】:

  • 忽略 IDisposable 可能会导致应该在有限数量的内存中运行的程序(如果正确使用 IDisposable)需要无限量的内存。如果程序内存需求的不必要的无限增长被认为是内存泄漏,那么忽略 IDisposable 可能会导致内存泄漏。
  • @Supercat:出于上述原因,总秃头。也许您的意思是“有限数量的资源”,但这只是重复答案。
  • 假设一个 IEnumerator 订阅了一个 INotifyCollectionChangedEvent 以允许实际枚举一个变化的集合(一个有用的范例,顺便说一句,并且由 VisualBasic.Collection 类支持)。除非集合和枚举器都包含一些复杂的逻辑以确保最终确定(这样的逻辑并不容易,并且会增加相当大的开销),否则未 Disposed 的 IEnumerator 将其寿命延长至集合的寿命。由于一个集合可以被枚举的次数没有限制......
  • ...在释放枚举数之前必须创建并保存在内存中的枚举数的数量也没有限制。不管一台机器有多少内存,如果一个进程在集合的生命周期内枚举列表太多次,它都会被用完。尽管每次枚举列表可能不会增加应用程序的有用状态量。
  • 如果不是 IDisposable.Dispose,您会建议 IEnumerator 使用什么模式来清理其事件?我建议,除非这样的设计非常不切实际或不可能,否则所有需要显式清理的对象都应在调用其 IDisposable.Dispose 方法时执行所有必要的清理。我认为任何需要调用其他方法的设计都是高度可疑的。
【解决方案2】:

我不会导致托管内存泄漏。它可能导致引用的非托管代码泄漏。但比这更糟糕的是:现代系统上的内存足够丰富,以至于您经常可以在一段时间内遇到严重的泄漏。见证 Mozilla Firefox:它曾经(现在仍然如此吗?)像筛子一样泄漏,数百万人乐于使用它。

更大的问题是可能与内存完全无关的其他资源。示例包括数据库连接、系统 I/O 句柄、套接字句柄、文件句柄等。如果您不小心正确使用 IDisposable,这些都是您可以轻松地在您自己的系统上创建拒绝服务情况的项目。

【讨论】:

  • 为一个长期存在的对象创建和放弃无限数量的事件订阅者的程序将需要无限量的内存来保存这些订阅者,即使在任何给定的非放弃订阅者的数量时间永远不会超过一个常数(或者,就此而言,一个)。这种废弃的订阅肯定会满足我对内存泄漏的定义(程序处理某些输入序列所需的内存量相对于它所持有的“有用”状态的数量是无限的)。
【解决方案3】:

只是在 Henk 和 Joel 的回答中添加一点内容

您所描述的情况经常发生在 DB Connections 上。 ADO.NET Performance counter NumberOfReclaimedConnections 已经足够了。这个计数器跟踪...

通过垃圾回收的连接数 应用程序未调用 Close 或 Dispose 的集合。 不明确关闭或处置连接会损害性能。

性能损失通常是等待释放连接所需的时间更长。这也可能导致连接超时,而不是内存问题。

【讨论】:

    【解决方案4】:

    如果IDisposable 对象有一个释放非托管内存的终结器,那么当终结器被调用时(在它被 GC 标记为收集并放入终结器队列之后),内存将是空闲的,但如果有不是任何终结器,并且永远不会调用 Dispose(),然后内存可能会泄漏,并且只有在进程终止时才会重新声明。

    【讨论】:

      【解决方案5】:

      未能对订阅来自较长生命周期对象的事件的对象调用 IDisposable 将延长订阅者的内存分配生命周期,从而延长到发布者的生命周期。如果在发布者的生命周期内可以附加和放弃的订阅者数量没有上限,这将构成无限内存泄漏。

      【讨论】:

        【解决方案6】:

        最大的问题是何时 GC 运行。

        参加以下课程

        class GcTest
        {
            private Stopwatch sw = new Stopwatch();
            public GcTest()
            {
                sw.Start();
            }
        
            ~GcTest()
            {
                sw.Stop();
                Console.WriteLine("GcTest finalized in " + sw.ElapsedMilliseconds + " ms");
            }
        }
        

        如果你愿意,可以在 Console.WriteLine 上设置断点。

        创建一个空的 windows 窗体应用程序,并在窗体加载事件中实例化一个新的GcTest

        private void Form1_Load(object sender, EventArgs e)
        {
            var gcTest = new GcTest();
        }
        

        运行您的应用程序并等待终结器运行。
        它很可能在您关闭应用程序之前不会运行。

        【讨论】:

          【解决方案7】:

          据我所知,GC 不会调用 Dispose。您必须自己显式调用它或使用 using。所以答案是:是的,如果该类处理在 Dispose 中释放的非托管资源,如果您不调用 Dispose,您的类就会泄漏。

          【讨论】:

          • 这并不严格,因为它只会在终结器运行之前“泄露”。这是基于他们正确实施IDisposable 的大假设。
          • 但是任何合适的 IDisposable 类也会释放析构函数 (GC) 中的资源。
          • @Henk Holterman:IDisposable 类实际上可以使用 Finalize 从不当放弃中恢复。然而,这种恢复并不总是可行的。在不知道那些特定对象可以安全地放弃的情况下放弃 IDisposable 对象的代码比 IDisposable 对象在被放弃时使事物处于不良状态的情况要严重得多。
          猜你喜欢
          • 2021-03-23
          • 2021-09-25
          • 2014-12-08
          • 1970-01-01
          • 1970-01-01
          • 2010-09-09
          • 2011-06-16
          • 2011-10-28
          相关资源
          最近更新 更多