“内存泄漏”剖析

在.NET的角度来看:

  • 什么是 内存泄漏
  • 你如何确定你的应用程序是否泄露?有什么影响?
  • 如何防止内存泄漏?
  • 如果您的应用程序存在内存泄漏,当进程退出或死亡时它会消失吗?或者即使在进程完成后,应用程序中的内存泄漏也会影响系统上的其他进程?
  • 通过COM Interop和/或P / Invoke访问的非托管代码如何?
0
额外 编辑
意见: 8

15 答案

我将内存泄漏定义为一个对象,它在完成之后不会释放分配的所有内存。如果您在框架和第三方组件中使用Windows API和COM(即存在错误或未正确管理的非托管代码),我发现这可能会发生在您的应用程序中。我也发现在使用笔等特定对象后会出现问题。

我个人遇到了内存异常,这些异常可能会导致网络应用程序中的内存泄漏,但不是唯一的。 (OOM也可以来自固定,请参阅 Pinning Artical ) 。如果你没有得到OOM错误或者需要确认它是否是内存泄漏导致它,那么唯一的办法就是分析你的应用程序。

我也会尝试并确保以下内容:

a)实现Idisposable的所有东西都是使用finally块或使用语句(包括画笔,钢笔等)来处理的(有些人认为除此之外,什么都不做)

b)使用finally或using语句再次关闭任何具有close方法的东西(尽管我发现使用并不总是关闭,具体取决于是否在using语句之外声明了该对象)

c)如果您使用的是非托管代码/ Windows API,这些代码将在正确处理之后处理。 (一些有清理方法释放资源)

希望这可以帮助。

0
额外

我想在一个托管的环境中,泄漏会让你不必要地引用大量的内存。

0
额外

我会同意Bernard在.net中的内容泄漏。

你可以剖析你的应用程序来查看它的内存使用情况,并确定如果它管理大量的内存时,它不应该是你可以说它有泄漏。

按照管理条款,我会把我的脖子放在线上,说它一旦死亡/被移除就会消失。

非托管代码是它自己的野兽,如果它内部存在泄漏,它将遵循标准的mem。泄漏定义。

0
额外
我想在一个受管理的环境中,一个   泄漏将是你保持一个   不必要的引用大块   的内存。

绝对。此外,在适当的时候不使用可丢弃对象上的.Dispose()方法会导致内存泄漏。最简单的方法是使用块,因为它会在最后自动执行.Dispose():

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

如果你创建一个使用非托管对象的类,如果你没有正确实现IDisposable,你可能会导致你的类的用户发生内存泄漏。

0
额外

严格来说,内存泄漏消耗了程序“不再使用”的内存。

“不再使用”有多个含义,它可能意味着“不再提及它”,也就是完全不可恢复的,或者可能意味着,被引用,可恢复,未被使用,但程序保留参考。只有后者适用于完美托管对象的.Net。然而,并不是所有的类都是完美的,并且在某些时候,底层的非托管实现可能会永久性地泄露资源。

在所有情况下,应用程序消耗的内存都比严格需要的要多。双方的影响,取决于泄露的数量,可能从无到有,过度收集造成的放缓,最终导致一系列内存异常,最后导致严重错误,随后强制进程终止。

当监视显示在每个垃圾收集周期之后,越来越多的内存分配给进程时,您知道应用程序存在内存问题。在这种情况下,您要么记忆太多,要么底层的非托管实现泄漏。

对于大多数泄漏,资源在进程终止时被恢复,但是在某些精确情况下,某些资源并不总是被恢复,GDI游标句柄因此而臭名昭着。当然,如果你有一个进程间通信机制,在另一个进程中分配的内存不会被释放,直到该进程释放或终止。

0
额外

我见过的最好的解释是在免费的编程电子书的基础

基本上,在.NET中,当被引用的对象被植入时会发生内存泄漏,因此无法进行垃圾收集。当您坚持超出预期范围的引用时,会发生意外。

当你开始出现内存耗尽或内存使用超出预期(perfmon具有很好的内存计数器)时,你会知道你有泄漏。

了解.NET的内存模型是避免它的最好方法。具体来说,了解垃圾收集器如何工作以及参考如何工作(再次,我将向您介绍电子书的第7章)。另外,请注意常见的陷阱,可能是最常见的事件。如果对象A注册到对象B上的事件,则对象A将一直存在,直到对象B消失,因为B持有对A的引用。解决方案是在完成后取消注册事件。

当然,一个好的内存配置文件可以让你看到你的对象图并探索你的对象的嵌套/引用,以查看引用来自何处以及根对象是负责的(红门蚂蚁个人资料,JetBrains dotMemory, memprofiler 是非常好的选择,或者您可以使用纯文本windbg和sos,但我强烈建议您使用商业/视觉产品,除非您是真正的专家)。

我相信非托管代码会受到未经处理的代码的典型内存泄露,但两者之间共享的引用由垃圾收集器管理。这最后一点可能是错误的。

0
额外
@Jeffry这是一种描述发生了什么并且我喜欢它的非常方式!
额外 作者 Exitos,
@kyoryu:一个对象是如何根源的?
额外 作者 Andrei Rînea,
哦,你喜欢书吗?我见过作者不时在stackoverflow上弹出。
额外 作者 Johnno Nolan,
一些.NET对象也可以自己创建并变得无法收集。因为这个,所有的IDisposable都应该被丢弃。
额外 作者 kyoryu,
@Andrei:我认为一个正在运行的Thread也许是对象本身的最好例子。一个将自己的引用放置在静态非公共位置的对象(例如订阅静态事件,或者通过静态字段初始化来实现单例)可能已经扎根于自身,因为没有明显的方法来... um将它从系泊中“拔除”。
额外 作者 Jeffrey Hantin,
非托管/本地代码通常更容易受到物理泄漏的影响,但不是那些逻辑/根源性代码。这是因为如果我们有这些根源资源中的一个不能“连根拔起”,那么通常它们最终会以C这样的语言被明确销毁。在这些情况下发生的错误更多的是悬挂指针,并且通常是某种类型的指针与访问它相关的段错误。虽然有时候实际上最好是让这种根源化的资源在应用程序关闭之前消耗内存(崩溃是非常令人讨厌的,易于检测到的错误)。
额外 作者 Team Upvote,

所有内存泄漏都通过程序终止来解决。

泄漏足够的内存,操作系统可能会决定以您的名义解决问题。

0
额外

如果您需要在.NET中诊断内存泄漏,请检查以下链接:

http:// msdn .microsoft.com / EN-US /杂志/ cc163833.aspx

http:// msdn .microsoft.com / EN-US /杂志/ cc164138.aspx

这些文章描述了如何创建流程的内存转储以及如何分析它,以便您可以首先确定您的泄漏是不受管理或管理的,以及是否管理,如何确定它来自哪里。

微软还有一个更新的工具来协助生成崩溃转储,以取代名为DebugDiag的ADPlus。

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en

0
额外

Using CLR Profiler from Microsoft http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en is a great way to determine which objects are holding memory, what execution flow leads to the creation of these objects, and also monitoring which objects live where on the heap (fragmentation, LOH, etc.).

0
额外

另外请记住,.NET有两个堆,一个是大对象堆。我相信大约85k或更大的物体被放在这个堆上。这个堆具有与常规堆不同的生命周期规则。

如果您正在创建大型内存结构(Dictionary或List),那么仔细查看确切规则是什么。

至于在进程终止时回收内存,除非你运行Win98或它的等价物,否则一切都会在终止时被释放回操作系统。唯一的例外是跨进程打开的事情,另一个进程仍然打开资源。

COM对象可能会很棘手。如果您始终使用 IDispose 模式,那么您将很安全。但是我已经运行了一些实现 IDispose 的互操作程序集。这里的关键是在完成之后调用 Marshal.ReleaseCOMObject 。 COM对象仍然使用标准的COM引用计数。

0
额外

我发现在.net找到内存泄漏时的.Net内存分析器一个很好的帮助。这不是免费的,如微软CLR分析器,但速度更快,更在我看来一点。一个

0
额外

我认为“什么是内存泄漏”和“有什么影响”的问题已经得到了很好的回答,但是我想在其他问题上增加更多的内容......

如何了解您的应用程序是否泄露

一种有趣的方式是打开 perfmon 并为所有堆中的#字节添加跟踪#Gen 2集合,每种情况下只需查看处理。如果执行特定功能会导致总字节数增加,并且该内存在下一代Gen 2收集之后仍然分配,那么您可能会说该功能会泄漏内存。

如何防范

其他好的意见已经给出。我只是补充说,也许是.NET内存泄漏最常被忽视的原因是将事件处理程序添加到对象而不删除它们。附加到对象的事件处理程序是对该对象的引用形式,因此即使在所有其他引用都去之后也会阻止收集。一定要记住分离事件处理程序(在C#中使用 - = 语法)。

流程退出时泄漏是否消失,COM互操作如何?

当你的进程退出时,所有映射到其地址空间的内存都被操作系统回收,包括从DLL提供的任何COM对象。相对而言很少,COM对象可以通过单独的进程提供服务。在这种情况下,当您的进程退出时,您可能仍然对在您使用的任何COM服务器进程中分配的内存负责。

0
额外

垃圾收集器如何工作的最佳解释是在Jeff Richters CLR via C# /(第20章)。阅读这篇文章为理解物体如何持续存在奠定了坚实的基础。

意外生根对象的最常见原因之一是将事件拖到课堂外。如果你连接一个外部事件

例如

SomeExternalClass.Changed += new EventHandler(HandleIt);

并且在处理时忘记解开它,然后SomeExternalClass会为您的类提供引用。

如上所述,

科技记忆分析器非常适合向您展示您怀疑发生泄漏的物体的根源。

但是也有一种非常快速的方法来检查特定的类型,只是使用WnDBG(甚至可以在VS.NET立即窗口中使用它):

.loadby sos mscorwks
!dumpheap -stat -type 

Now do something that you think will dispose the objects of that type (例如 close a window). It's handy here to have a debug button somewhere that will run System.GC.Collect() a couple of times.

Then run !dumpheap -stat -type again. If the number didn't go down, or didn't go down as much as you expect, then you have a basis for further investigation. (I got this tip from a seminar given by Ingo Rammer).

0
额外

为什么人们认为.NET中的内存泄漏与其他任何泄漏都不一样?

内存泄漏是当你连接到一个资源时,不要放过它。您可以在托管编码和非托管编码中执行此操作。

关于.NET和其他编程工具,已经有关于垃圾收集的想法,以及其他将使应用程序泄漏的情况最小化的方法。 但是防止内存泄漏的最好方法是你需要在你使用的平台上理解你的底层内存模型以及事物的工作方式。

相信GC和其他魔法将清理你的混乱是内存泄漏的简短方式,并且以后很难找到。

在编写非托管代码时,通常要确保清理,你知道你掌握的资源将是你清理的责任,而不是清理员的责任。

另一方面,在.NET中,很多人认为GC会清理所有的东西。那么,它为你做了一些,但你需要确保它是如此。 .NET确实包装了很多东西,所以你并不总是知道你是在处理一个托管的还是非托管的资源,并且你需要确定你正在处理的是什么。处理字体,GDI资源,活动目录,数据库等是你需要注意的事情。

按照管理条款,我会放下我的脖子   该行说它确实消失了一次   该进程被终止/删除。

我看到很多人都有这个,我真的希望这会结束。你不能要求用户终止你的应用程序来清理你的混乱! 看一下浏览器,可以是IE,FF等,然后打开Goog​​le Reader,让它停留几天,看看会发生什么。

如果您然后在浏览器中打开另一个选项卡,请浏览某个网站,然后关闭托管其他页面的选项卡以使浏览器泄漏,您认为浏览器会释放内存吗?与IE不同。在我的电脑上,如果我使用Google Reader,IE会在短时间内(大约3-4天)轻松地吃掉1 GiB的内存。有些新闻报道更糟糕。

0
额外

One definition is: Unable to release unreachable memory, which can no longer be allocated to new process during execution of allocating process. It can mostly be cured by using GC techniques or detected by automated tools.

欲了解更多信息,请访问 http://all-about-java-and-weblogic-server.blogspot.in/2014/01/what-is-memory-leak-in-java.html

0
额外