托管 (.net) 应用程序中内存泄漏的最常见(并且经常被忽视)的原因是啥?

Posted

技术标签:

【中文标题】托管 (.net) 应用程序中内存泄漏的最常见(并且经常被忽视)的原因是啥?【英文标题】:What are the most common (and often overlooked) causes of memory leaks in managed (.net) applications?托管 (.net) 应用程序中内存泄漏的最常见(并且经常被忽视)的原因是什么? 【发布时间】:2010-10-14 22:35:39 【问题描述】:

请任何人推荐一个快速清单/最佳实践指南,以帮助我们避免可能导致 .net 应用程序内存泄漏的简单(但微妙)错误

当我处于项目的测试阶段时,我发现开始寻找内存泄漏的原因非常困难且非常痛苦。

如果有“经验法则”可以完全指导托管应用程序中的内存泄漏,我恳请您分享您的经验。

谢谢。

(我认为托管应用程序应该是“内存托管”,即 GC?为什么我们仍然会在纯托管代码中发现泄漏?)

【问题讨论】:

顺便说一句——通过 C# 的 CLR 这本书有一个非常好的部分关于 .NET 自动内存管理。我认为该部分基于 Jeffrey Richter 曾经为 MSDN 撰写的一篇文章,因此您可以在档案中找到它,尽管这本书可能更好。 【参考方案1】:

泄漏的形式有很多种:

非托管泄漏(分配非托管代码的代码) 资源泄漏(分配和使用非托管资源的代码,如文件、套接字) 延长对象的生命周期 对 GC 和 .NET 内存管理工作原理的错误理解 .NET 运行时中的错误

前两个通常由两段不同的代码处理:

在对象上实现 IDisposable 并在 Dispose 方法中释放非托管内存/资源 实现终结器,以确保在 GC 发现对象符合收集条件时释放非托管资源

然而,第三个是不同的。

假设您正在使用一个包含数千个对象的大列表,总共占用大量内存。如果您保留对该列表的引用的时间超过您需要的时间,您将遇到看起来像内存泄漏的情况。另外,如果你不断地往这个列表中添加,让它周期性地随着更多的数据而增长,而旧的数据永远不会被重用,那么你肯定有内存泄漏。

我经常看到的一个来源是将方法附加到事件处理程序,但在完成后忘记取消注册它们,从而使事件处理程序的大小和要执行的代码慢慢膨胀。

第四,对 .NET 内存管理工作原理的错误理解可能意味着您在进程查看器中查看内存使用情况并注意到您的应用程序的内存使用情况不断增长。如果您有大量可用内存,GC 可能不会经常运行,从而为您提供当前使用内存的错误图片,而不是映射内存。

第五个,这更难,到目前为止,我只在 .NET 中看到一个资源管理错误,并且已经计划在 .NET 4.0 中修复它,它是将桌面屏幕复制到 .NET 图像中。


编辑:针对 cme​​ts 中的问题,如何避免保持引用时间过长,那么唯一的方法就是这样做。

让我解释一下。

首先,如果您有一个长时间运行的方法(例如,它可能正在处理磁盘上的文件、下载某些东西或类似的东西),并且您在该方法的早期使用了对大数据结构的引用,之前长时间运行的部分,然后您不将该数据结构用于该方法的其余部分,然后是.NET,在发布版本中(而不是在调试器下运行)足够聪明,可以知道这个引用,尽管它保存在技术上范围内的变量中,有资格进行垃圾收集。垃圾收集器在这方面非常激进。在调试构建和在调试器下运行时,它将在方法的生命周期内保留引用,以防您想在断点处停止时检查它。

但是,如果引用存储在声明该方法的类中的字段引用中,那么它就不那么聪明了,因为无法确定它是否会在以后被重用,或者至少非常非常困难。如果这个数据结构变得不必要了,你应该清除你持有的引用,以便 GC 稍后会捡起它。

【讨论】:

>>假设您正在使用一个包含数千个对象的大列表,有什么办法可以解决这个问题。我们很多人都这样做。我能想到的就是从堆栈中分配和重用现有对象,这样就不会在大列表中留下未使用的对象【参考方案2】:

简短的回答是不明显的参考。

添加一些细节:

在收集 AppDomain 之前不会收集统计信息(这可能等于进程关闭) 活动(记得取消订阅) 阻塞的终结器(终结器按顺序运行,因此任何阻塞的终结器都会阻止收集所有其他可终结对象)。示例包括无法访问 STA 线程的终结器。 死锁线程永远不会释放根 忘记调用 Monitor.Exit()(例如,与超时或跨方法一起使用时)可能会导致死锁,进而可能导致泄漏

【讨论】:

【参考方案3】:

第一个是附加的且从不分离的事件处理程序。

如果事件的订阅者的寿命比事件的生产者长,并且订阅者没有断开事件处理程序的连接,那么触发事件的对象将保持活动状态,因为仍然存在通过订阅对象对其的引用。

【讨论】:

我认为你把这个弄反了。发布者的寿命比订阅者的寿命长……这就是您遇到问题的时候。查看***.com/questions/4526829/… 进行验证。【参考方案4】:

回答你最后两个问题:

根据定义,托管代码中没有内存泄漏。可能发生两种泄漏:

当没有对对象的引用处于活动状态时,将释放对象。如果你仍然有一个对象的引用,它不会被释放。例如,当您丢弃对已注册事件的对象的引用时,就会发生这种情况 - 如果您不手动取消注册事件处理程序(或使用弱引用),事件仍将引用该对象,因此它不会被释放,尽管你不再有明显的参考。 非托管资源可能会泄露。通常,非托管资源的包装器实现 IDisposable 并在您调用 Dispose 时释放资源。如果你只是扔掉这个对象,它不会释放资源,因此会泄漏它。

所以,两个经验法则是:

在释放对象时取消注册任何事件处理程序,或为此使用弱引用(搜索 SO,在某处有解释)。 如果类公开了 Dispose 方法,请调用它。如果您只是临时使用该对象,请使用 using 构造。如果您有实现 IDisposable 的成员,请自行实现 IDisposable 并在您的 Dispose 中调用成员的 Dispose。

【讨论】:

【参考方案5】:

您可以做的许多好事之一来有效地管理内存。

尽可能在任何实现 IDisposable 的对象上调用 dispose,尤其是在 DataSet 或 DataTable 对象上。

最好还是在这些对象上使用 using 构造。

【讨论】:

+1 for the USING - 这在过去帮助我解决了明确的内存泄漏问题!【参考方案6】:

在我看来,托管应用程序中内存泄漏的第一大原因是错误地认为您有内存泄漏,而实际上您没有正确测量。一定要使用内存分析器来准确测量你有什么样的内存泄漏。您可能会发现您还有其他问题。

但是当泄漏是真实的时,主要原因是对对象的引用使这些对象在不需要时保持活动状态。为几乎所有实现 IDisposable 的对象实现 using 语句是一个好的开始。我知道的唯一例外是 WCF 代理类,它应该使用 try/catch/finally 访问,而不是 using 语句。

【讨论】:

任何文章的参考为什么应该使用 try/catch/finally 而不是使用 WCF 代理?【参考方案7】:

我可以开始建议人们应该始终注意IDisposable 对象以及如何正确处置它们。另外,要非常小心应用中的状态仅在绝对必要时抓取对象。如果对象 A 将在您的应用中存在很长时间,请始终尝试不要在 A 和其他对象之间建立依赖关系。

【讨论】:

【参考方案8】:

值类中的循环引用(通常在应用程序的模型层中)也可以轻松隐藏它们的泄漏。理论上,不要这样做,在实践中,当你需要做的时候要注意:)

【讨论】:

【参考方案9】:

检查使用后不需要在内存中的静态变量,检查事件处理引用,阻止收集其他终结器的任何终结器,以及是否存在对任何 DLL 的非托管引用或COM+ 对象

【讨论】:

【参考方案10】:

以下是内存泄漏的主要原因。

    持有对托管对象的引用(未释放事件处理程序的经典案例。) 无法释放非托管资源 无法处理绘图对象 我注意到计时器也会导致内存泄漏。如果您正在使用任何计时器,请务必将其丢弃。

请在http://blogs.msdn.com/b/davidklinems/archive/2005/11/16/three-common-causes-of-memory-leaks-in-managed-applications.aspx进一步阅读

【讨论】:

【参考方案11】:

内存泄漏可能来自多种来源。

您注册了一个活动,但忘记取消注册该活动。

文件/连接已打开但未正确关闭。

Dispose() 调用未正确实现。

Dispose() 调用以某种方式被绕过;比如中间遇到了异常。

您遇到了死锁(可能导致无法释放根对象)。

终结器线程被阻塞;例如,COM 对象的 STA 线程不可用。

非托管代码存在泄漏。

对象的生命周期太长。

存在循环引用。

泄漏可能来自 .NET 运行时环境(在极少数情况下)。

您的测试框架也可能出现泄漏。 (在这种情况下,它 是测试泄漏,不是开发环境泄漏,并且测试 工程师负责解决。)

【讨论】:

以上是关于托管 (.net) 应用程序中内存泄漏的最常见(并且经常被忽视)的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

记一次 .NET 某打印服务 非托管内存泄漏

记一次 .NET 某工控视觉软件 非托管泄漏分析

记一次 .NET 某智慧水厂API 非托管内存泄漏分析

记一次 .NET 某电厂Web系统 内存泄漏分析

记一次 .NET 某智慧水厂API 非托管内存泄漏分析

记一次 .NET 某桌面奇侠游戏 非托管内存泄漏分析