.NET中的内存泄漏
.NET中出现内存泄漏的所有可能方式有哪些?
我知道两个:
- 未正确注销事件处理程序/代理。
- 不在Windows窗体中放置动态子控件:
例子:
// Causes Leaks Label label = new Label(); this.Controls.Add(label); this.Controls.Remove(label); // Correct Code Label label = new Label(); this.Controls.Add(label); this.Controls.Remove(label); label.Dispose();
更新:想法是列出不太明显的常见陷阱(例如上述)。通常,由于垃圾收集器,内存泄漏不是大问题。不像以前那样用C ++。
很棒的讨论人员,但是让我澄清一下...根据定义,如果.NET中没有对某个对象的引用,那么它将在某个时间被垃圾收集。因此,这不是诱发内存泄漏的方法。
在托管环境中,如果我们意外引用了我们不知道的任何对象,那么我会认为这是内存泄漏(因此,我的问题中有两个示例)。
那么,发生内存泄漏的各种可能方式有哪些?
解决方案
回答
无法提供全面的清单...这很像在问"你怎么弄湿?"
也就是说,请确保对实现IDisposable的所有对象都调用Dispose(),并确保对消耗任何类型的非托管资源的任何类型都实现IDisposable。
时不时地在代码库上运行类似FxCop之类的内容以执行该规则,我们会惊讶于一些一次性对象被埋在应用程序框架中的深度。
回答
我们是在谈论意外的内存使用情况还是实际的泄漏?我们列出的两种情况并不完全是泄漏;在某些情况下,物体停留的时间比预期的更长。
换句话说,他们是引用他们的人,他们不知道或者忘记了内存泄漏。
编辑:或者它们是垃圾收集器或者非托管代码中的实际错误。
编辑2:考虑此问题的另一种方法是始终确保适当释放对对象的外部引用。外部表示代码超出控制范围。发生这种情况的任何情况都是我们可以"泄漏"内存的情况。
回答
许多可能导致非托管语言中的内存泄漏的事情仍然可以导致托管语言中的内存泄漏。例如,不良的缓存策略可能导致内存泄漏。
但是,正如格雷格(Greg)和丹尼(Danny)所说的那样,没有完整的清单。任何在其使用寿命内可能导致保持内存的内容都可能导致泄漏。
回答
每次调用IDisposable是最容易开始的地方,并且绝对是抓住代码库中所有低挂内存泄漏结果的有效方法。但是,这并不总是足够的。例如,了解在运行时如何以及何时生成托管代码也很重要,并且一旦程序集加载到应用程序域中,就永远不会卸载它们,这会增加应用程序的占用空间。
回答
那实际上并不会导致泄漏,只会为GC带来更多工作:
// slows GC Label label = new Label(); this.Controls.Add(label); this.Controls.Remove(label); // better Label label = new Label(); this.Controls.Add(label); this.Controls.Remove(label); label.Dispose(); // best using( Label label = new Label() ) { this.Controls.Add(label); this.Controls.Remove(label); }
在像.Net这样的托管环境中,像这样放置一次性组件从来都不是什么大问题,这是托管手段的重要组成部分。
当然,我们会降低应用程序的运行速度。但是,我们不会为其他任何事情所困扰。
回答
Finalize(或者从Finaliser进行Dispose调用)方法中的异常会阻止正确处理非托管资源。
一种常见的情况是由于程序员假定要处理的对象顺序不同,并试图释放已被处理的对等对象,从而导致异常,而Finalize / Dispose from Finalize方法的其余部分未被调用。
回答
阻止终结器线程。在终结器线程被解除阻塞之前,不会其他任何对象被垃圾回收。因此,使用的内存量将不断增长。
进一步阅读:http://dotnetdebug.net/2005/06/22/blocked-finalizer-thread/
回答
死锁线程永远不会释放根。显然,我们可能会认为僵局带来了更大的问题。
死锁的终结器线程将阻止所有剩余的终结器运行,从而阻止回收所有可终结对象(因为它们仍由易碎列表作为根)。
在多CPU计算机上,创建终结对象的速度比终结器线程运行终结器的速度快。只要持续下去,我们就会"泄漏"内存。这种情况不太可能在野外发生,但很容易复制。
大对象堆未压缩,因此我们可能会通过碎片泄漏内存。
有许多必须手动释放的对象。例如。远程处理没有租约和程序集的对象(必须卸载AppDomain)。
回答
直接设置GridControl.DataSource属性,而无需使用BindingSource类的实例(http://msdn.microsoft.com/zh-cn/library/system.windows.forms.bindingsource.aspx)。
这导致我的应用程序泄漏,使我花了相当长的时间才能通过探查器进行跟踪,最终我发现此错误报告得到了Microsoft的响应:http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID= 92260
有趣的是,Microsoft在BindingSource类的文档中尝试将其作为合法的,经过深思熟虑的类进行传递,但我认为他们只是创建它来解决有关货币管理器和将数据绑定到网格控件的根本性漏洞。
注意这一点,我敢打赌,绝对有大量的泄漏应用程序在此运行!
回答
- 保留对不再需要的对象的引用。
关于其他注释,确保调用Dispose的一种方法是在代码结构允许时使用using...。
回答
为防止.NET内存泄漏:
1)每当创建具有" IDisposable"接口的对象时,均应使用"使用"结构(或者"最终尝试"结构)。
2)如果类创建线程或者将对象添加到静态或者长期存在的集合,则使它们为" IDisposable"。请记住,C'event'是一个集合。
这是一篇有关防止内存泄漏的提示的简短文章。
回答
我真正出乎意料的一件事是:
Region oldClip = graphics.Clip; using (Region newClip = new Region(...)) { graphics.Clip = newClip; // draw something graphics.Clip = oldClip; }
内存泄漏在哪里?是的,我们也应该配置过" oldClip"!因为" Graphics.Clip"是罕见的属性之一,每次调用getter时都会返回一个新的一次性对象。
回答
我还有4个其他项目要添加到此讨论中:
- 终止未正确准备此类事件而创建UI控件的线程(Thread.Abort())可能会导致预期使用内存。
- 通过Pinvoke访问非托管资源而不清理它们可能会导致内存泄漏。
- 修改大字符串对象。不一定是内存泄漏,一旦超出范围,GC就会解决它,但是,从性能角度来看,如果经常修改大字符串,系统可能会受到打击,因为我们不能真正依赖GC来确保程序的足迹最小的。
- 通常创建GDI对象以执行自定义绘图。如果经常执行GDI工作,请重用单个gdi对象。
回答
苔丝·费尔南德斯(Tess Fernandez)关于发现和调试内存泄漏的博客文章很棒。
实验6实验7