内存泄漏C#

时间:2011-02-16 18:57:15

标签: c# memory-leaks

我试图更好地理解内存泄漏的概念。任何人都可以指出一些有用的信息,这些信息可以帮助我更好地理解内存泄漏是什么以及我如何在我的代码中找到它们。

6 个答案:

答案 0 :(得分:41)

存在多种内存泄漏,但一般来说,该术语指的是某种不再使用的资源,但仍会占用内存。如果你有很多这样的应用程序需要大量的内存,最终你会用完它。

在C#中,这些是常见的内存泄漏:

  • 不删除事件侦听器。使用匿名方法或引用外部对象的lambda表达式创建的任何事件侦听器都将使这些对象保持活动状态。请记住在不再使用事件侦听器时将其删除。
  • 在不使用数据库连接或结果集时保持打开状态。请记得在所有Dispose()个对象上调用IDisposableUse the using statement
  • 使用p / Invoke调用C函数,它分配您从未发布的内存。

答案 1 :(得分:27)

当您分配内存时会发生传统的内存泄漏,然后以某种方式“忘记”释放内存。在旧的C ++代码中,这意味着在没有相应的new的情况下调用delete。在C中,这意味着在没有相应alloc()的情况下拨打malloc() / free()

在.Net中,传统意义上的没有内存泄漏,因为你不应该自己释放内存。相反,您依靠垃圾收集器为您释放它。但是,这并不意味着你永远不会忘记记忆。有几种方法可能会意外地保留引用,以防止垃圾收集器执行它的工作。这些包括全局变量(特别是列表,字典和可能用于“缓存”对象的其他集合类型),挂起到内存的事件处理程序,递归历史引用和大对象堆。

这里还要注意的是,仅仅因为你看到了.Net中增加内存使用的模式,这并不一定意味着你的应用程序正在泄漏内存。在整体内存压力较低的情况下,垃圾收集器可能只是选择通过不收集来节省时间,或者只是在进程的现有地址空间内收集而不将内存返回给操作系统。

答案 2 :(得分:13)

非常好的阅读是Everybody thinks about garbage collection the wrong way

通常,内存泄漏或任何资源泄漏都是程序分配内存(或任何其他资源),然后在完成后省略解除分配。在本机应用程序中,内存泄漏是最常见的资源泄漏,并且可能在资源引用(指向已分配块的指针)超出范围并被销毁时发生,但分配的资源(内存块)不会被破坏。在这种情况下,资源(内存)被泄露,因为程序已经失去释放它的能力,即使它想要,因为它不再记住资源的位置(块的地址)。

在托管应用程序中,内存泄漏有点棘手。由于运行时可以自动跟踪对资源的引用,因此它还可以了解资源(对象)何时不再被应用程序的任何活动部分引用(在任何线程上没有从堆栈帧到该资源的引用链)因此运行时可以理解何时可以安全地收集应用程序不再引用的对象。因此,在托管世界中,如果您认为应用程序不再引用对象(因此可以由运行时收集),则会发生“泄漏”,但事实上,通过某些引用链,您参考它,因此无法收集。

我强烈推荐上面链接的Raymond Chen的文章,非常有启发性。

答案 3 :(得分:6)

当将内存分配给应用程序时,应用程序有义务将该内存释放回操作系统,以便其他应用程序可以重用它。当应用程序未释放该内存时会发生内存泄漏,从而阻止其重新分配。

对于托管代码,垃圾收集器跟踪对应用程序创建的对象的引用。对于大多数情况,CLR将代表正在运行的进程以合理的方式透明地处理内存分配和释放。然而,.NET开发人员仍然需要考虑资源管理,因为尽管垃圾收集器的工作仍然存在内存泄漏的情况。

请考虑以下代码:

Widget widget = new Widget();

上面的代码行创建了Widget类的新实例,并为widget字段分配了对该对象的引用。 GC跟踪与每个对象关联的引用,并释放没有强引用的对象的内存。

值得一提的是,CLR的垃圾收集只会收集托管对象,.NET代码可以并且经常使用不能自动垃圾收集的非托管资源。

当分配了这些资源的对象在对这些资源的最后一次引用超出范围之前无法正确释放它们时,会发生非托管资源泄漏,从而使资源分配,但未引用,因此无法用于应用程序。

直接引用非托管资源的类应确保正确释放这些资源。这样做的一个例子如下:

public void ManagedObject : IDisposable
{
    //A handle to some native resource.
    int* handle;

    public ManagedObject()
    {
        //AllocateHandle is a native method called via P/Invoke.
        handle = AllocateHandle();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (disposing)
        {
            //deal with managed resources here
            FreeHandle(handle);
        }
    }

    ~ManagedType()
    {
        Dispose(false);
    }
}

从终结器调用时,disposing参数为false。这是为了防止在终结器中使用受管资源,因为在该阶段应将托管引用视为无效。

另请注意,Dispose()方法调用GC.SuppressFinalize(this),这会阻止为该实例运行终结器。这样做是因为在Dispose调用中取消分配了终结器中已经解除分配的资源,从而无需调用fializer。

使用处理非托管资源的类(或任何实现IDisposable的类)的客户端代码应在using块中执行此操作,以确保在访问资源时调用IDisposable.Dispose不再需要,因为这将处理托管和非托管资源,并且在上面的示例的情况下,确保不会对终结器进行非常昂贵的调用。

我的漫无目的的赞美诗。我现在就停下来。

答案 4 :(得分:4)

当您的程序动态分配在完成使用后无法正确释放的内存时,会发生内存泄漏。如果你有一个持续这样做的程序,你的泄漏会变得越来越大,很快你的程序占用了你所有的RAM。

答案 5 :(得分:4)

“内存泄漏”应定义为“当您认为不应该使用它时使用的内存”以C#/ Java的形式应用于垃圾收集语言/运行时。

传统上,“内存泄漏”被定义为未正确解除分配的内存(请参阅其他答案中的维基百科链接),这对于垃圾收集环境通常不会发生。请注意,由于运行时出现问题,即使垃圾收集语言也会泄漏内存 - 即使JavaScript是垃圾收集语言,也很容易在Internet Explorer的JavaScript运行时中泄漏大量JavaScript对象。