178

从 .NET 的角度来看:

  • 什么是内存泄漏
  • 如何确定您的应用程序是否泄漏?有什么影响?
  • 如何防止内存泄漏?
  • 如果您的应用程序有内存泄漏,它会在进程退出或被杀死时消失吗?或者,即使在进程完成后,您的应用程序中的内存泄漏是否会影响系统上的其他进程?
  • 那么通过 COM Interop 和/或 P/Invoke 访问的非托管代码呢?
4

15 回答 15

110

我见过的最好的解释是免费的Foundations of Programming 电子书的第 7 章。

基本上,在.NET中,当引用的对象被植根时会发生内存泄漏,因此无法进行垃圾回收。当您保留超出预期范围的引用时,会意外发生这种情况。

当您开始收到 OutOfMemoryExceptions 或您的内存使用量超出您的预期(PerfMon有很好的内存计数器)时,您就会知道您有泄漏。

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

当然,一个好的内存配置文件可以让您查看对象图并探索对象的嵌套/引用,以查看引用来自何处以及负责哪个根对象(红门蚂蚁配置文件,JetBrains dotMemory,memprofiler真的很好选择,或者您可以使用纯文本WinDbgSOS,但我强烈推荐商业/视觉产品,除非您是真正的大师)。

我相信非托管代码会受到其典型的内存泄漏的影响,除了共享引用由垃圾收集器管理。最后一点我可能是错的。

于 2008-08-01T15:28:12.543 回答
35

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

“不再使用”有不止一个含义,它可能意味着“不再引用它”,即完全不可恢复,或者它可能意味着已引用、可恢复、未使用,但程序仍然保留引用。只有后者适用于 .Net 的完美管理对象。但是,并非所有类都是完美的,并且在某些时候,底层的非托管实现可能会永久泄漏该进程的资源。

在所有情况下,应用程序消耗的内存都比严格需要的多。副作用,取决于泄漏的数量,可能从没有到过度收集导致的减速,到一系列内存异常,最后是致命错误,然后是强制进程终止。

当监控显示在每个垃圾回收周期后分配给您的进程的内存越来越多时,您就知道应用程序存在内存问题。在这种情况下,您要么在内存中保留了太多,要么某些底层的非托管实现正在泄漏。

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

于 2008-08-01T17:52:46.293 回答
32

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

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

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

如何预防

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

当进程退出时泄漏会消失吗?COM互操作呢?

当您的进程退出时,映射到其地址空间的所有内存都将由操作系统回收,包括由 DLL 提供的任何 COM 对象。比较少见的是,COM 对象可以从单独的进程中提供服务。在这种情况下,当您的进程退出时,您可能仍需对您使用的任何 COM 服务器进程中分配的内存负责。

于 2008-08-15T16:11:41.103 回答
18

我将内存泄漏定义为一个对象在完成后没有释放所有分配的内存。我发现如果您在框架和第三方组件中使用 Windows API 和 COM(即其中存在错误或未正确管理的非托管代码),则可能会在您的应用程序中发生这种情况。我还发现在使用某些物品(如钢笔)后不整理可能会导致问题。

我个人遇到过内存不足异常,这些异常可能是由 dot net 应用程序中的内存泄漏引起的,但不限于此。(OOM 也可以来自 pinning 参见Pinning Artical)。如果您没有收到 OOM 错误或需要确认是否是内存泄漏导致的,那么唯一的方法是分析您的应用程序。

我还将尝试确保以下内容:

a) 实现 Idisposable 的所有内容都使用 finally 块或 using 语句进行处理,这些语句包括画笔、钢笔等(有些人主张将所有内容设置为空)

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

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

希望这可以帮助。

于 2008-08-01T17:57:14.117 回答
18

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

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

这些文章描述了如何创建进程的内存转储以及如何分析它,以便您可以首先确定您的泄漏是非托管还是托管,如果它是托管的,如何确定它的来源。

微软还有一个更新的工具来帮助生成故障转储,以取代 ADPlus,称为 DebugDiag。

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

于 2008-08-14T23:16:39.803 回答
15

使用 Microsoft http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en的 CLR Profiler是确定哪些对象持有内存、执行流程如何的好方法到这些对象的创建,以及监视哪些对象在堆上的位置(碎片,LOH等)。

于 2008-08-16T19:54:30.497 回答
14

Jeff Richters CLR via C# book, (Ch. 20)中对垃圾收集器如何工作的最佳解释。阅读本文为理解对象如何持续存在提供了很好的基础。

意外生根对象的最常见原因之一是在类之外连接事件。如果您连接外部事件

例如

SomeExternalClass.Changed += new EventHandler(HandleIt);

并且在你处理时忘记解开它,那么 SomeExternalClass 对你的类有一个引用。

如上所述,SciTech 内存分析器非常擅长向您显示您怀疑正在泄漏的对象的根源。

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

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

现在做一些你认为会处理该类型对象的事情(例如关闭一个窗口)。System.GC.Collect()在某个地方有一个可以运行几次的调试按钮很方便。

然后!dumpheap -stat -type <TypeName>再次运行。如果这个数字没有下降,或者没有像你预期的那样下降,那么你就有了进一步调查的基础。(我从Ingo Rammer举办的研讨会上得到了这个提示)。

于 2008-08-27T14:12:23.633 回答
13

我猜在托管环境中,泄漏将是您保留对大量内存的不必要引用。

于 2008-08-01T15:19:25.900 回答
11

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

内存泄漏是当您附加到资源并且不放开它时。您可以在托管和非托管编码中执行此操作。

关于 .NET 和其他编程工具,有一些关于垃圾收集的想法,以及其他将导致应用程序泄漏的情况最小化的方法。但是防止内存泄漏的最佳方法是您需要了解您的底层内存模型,以及在您使用的平台上事情是如何工作的。

相信 GC 和其他魔法会清理你的烂摊子是内存泄漏的捷径,以后很难找到。

当编码不受管理时,您通常确保清理,您知道您掌握的资源将是您清理的责任,而不是看门人的。

另一方面,在 .NET 中,很多人认为 GC 会清理所有内容。好吧,它对你有一些帮助,但你需要确保它确实如此。.NET 确实封装了很多东西,因此您并不总是知道您是在处理托管资源还是非托管资源,您需要确定您在处理什么。处理字体、GDI 资源、活动目录、数据库等通常是您需要注意的事情。

在管理方面,我会说一旦进程被杀死/删除,它就会消失。

我看到很多人都有这种情况,我真的希望这会结束。你不能要求用户终止你的应用程序来清理你的烂摊子!看一下浏览器,可以是 IE、FF 等,然后打开,比如 Google Reader,让它停留几天,看看会发生什么。

如果您然后在浏览器中打开另一个选项卡,浏览某个站点,然后关闭托管另一个导致浏览器泄漏的页面的选项卡,您认为浏览器会释放内存吗?IE 则不然。如果我使用 Google Reader,在我的计算机上,IE 很容易在短时间内(大约 3-4 天)吃掉 1 GiB 的内存。有些新闻页面更糟糕。

于 2008-09-01T12:19:13.210 回答
9

我猜在托管环境中,泄漏将是您保留对大量内存的不必要引用。

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

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

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

于 2008-08-05T22:47:49.440 回答
8

所有内存泄漏都由程序终止解决。

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

于 2008-08-04T14:09:47.117 回答
7

我同意 Bernard 的观点,即在 .net 中内存泄漏是什么。

您可以分析您的应用程序以查看其内存使用情况,并确定它是否在不应该管理大量内存时说它有泄漏。

在管理方面,我会说一旦进程被杀死/删除,它就会消失。

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

于 2008-08-01T15:23:33.817 回答
6

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

如果您正在创建大型内存结构(字典或列表),那么谨慎地查找确切的规则是什么。

至于在进程终止时回收内存,除非您正在运行 Win98 或其等效版本,否则所有内容都会在终止时释放回操作系统。唯一的例外是跨进程打开的东西并且另一个进程仍然打开资源。

COM 对象可能很棘手。如果您始终使用该IDispose模式,您将是安全的。但是我遇到了一些实现IDispose. Marshal.ReleaseCOMObject这里的关键是当你完成它时打电话。COM 对象仍然使用标准的 COM 引用计数。

于 2008-08-07T13:17:27.547 回答
5

在 .Net 中查找内存泄漏时,我发现.Net Memory Profiler非常有用。它不像 Microsoft CLR Profiler 那样免费,但在我看来它更快、更贴切。一种

于 2008-08-20T18:39:25.953 回答
1

一种定义是:无法释放不可达内存,在分配进程执行过程中不能再分配给新进程。它主要可以通过使用 GC 技术来治愈或通过自动化工具检测。

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

于 2014-08-27T15:22:06.127 回答