内存泄漏C#

atr*_*joe 35 c# memory-leaks

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

Mar*_*son 43

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

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

  • 不删除事件侦听器.使用匿名方法或引用外部对象的lambda表达式创建的任何事件侦听器都将使这些对象保持活动状态.请记住在不再使用事件侦听器时删除它们.
  • 在不使用数据库连接或结果集时保持打开状态.记得要打电话Dispose()给所有IDisposable对象.使用该using声明.
  • 使用p/Invoke调用C函数,它分配你永远不会释放的内存.


Joe*_*orn 30

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

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

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

  • 如果没有智能指针,“旧 C++”会是 C++ 吗? (3认同)

Rem*_*anu 13

一个非常好的读物是每个人都以错误的方式思考垃圾收集.

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

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

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

  • 更新了 Raymond Chen 链接:https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13203 (2认同)

Cri*_*urf 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Nic*_*ndo 4

当您的程序动态分配内存但在使用完内存后未正确释放内存时,就会发生内存泄漏。如果你有一个程序不断地这样做,你的泄漏将会越来越大,很快你的程序就会占用你所有的内存。