.NET 中的内存泄漏[关闭]
-
09-06-2019 - |
题
.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 中没有对某个对象留下任何引用,则该对象有时会被垃圾收集。所以这不是引起内存泄漏的方法。
在托管环境中,如果您无意中引用了您不知道的任何对象(因此是我的问题中的两个示例),我会认为这是内存泄漏。
那么,发生这种内存泄漏的可能方式有哪些呢?
解决方案
阻塞终结器线程。在终结器线程解除阻塞之前,不会对其他对象进行垃圾回收。因此,使用的内存量会越来越大。
进一步阅读: http://dotnetdebug.net/2005/06/22/blocked-finalizer-thread/
其他提示
这并不会真正导致泄漏,它只是给 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 这样的托管环境中,像这样随意放置一次性组件从来都不是一个大问题 - 这是托管含义的重要组成部分。
当然,你会减慢你的应用程序的速度。但你不会为其他任何事情留下一团糟。
设置 GridControl.数据源 直接属性而不使用 BindingSource 类的实例(http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx).
这导致我的应用程序出现泄漏,我花了很长时间才用分析器进行追踪,最终我发现了微软回应的这个错误报告: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=92260
有趣的是,在 BindingSource 类的文档中,微软试图将其冒充为一个经过深思熟虑的合法类,但我认为他们创建它只是为了解决有关货币管理器和将数据绑定到网格控件的根本性泄漏。
小心这个,我敢打赌,绝对有大量的泄漏应用程序因此而存在!
没有办法提供完整的列表......这很像问“你怎么会被淋湿?”
也就是说,请确保在所有实现 IDisposable 的对象上调用 Dispose(),并确保在消耗任何类型的非托管资源的任何类型上实现 IDisposable。
时不时地在您的代码库上运行类似 FxCop 的东西来帮助您执行该规则 - 您会惊讶于一些一次性对象在应用程序框架中的埋藏有多深。
Finalize(或来自 Finaliser 的 Dispose 调用)方法中的异常会阻止正确处置非托管资源。常见的一个是由于程序员 假设 对象将被处置的顺序以及尝试释放已处置的对等对象会导致异常,并且 Finalize 方法中的 Finalise/Dispose 的其余部分不会被调用。
我还有 4 个附加项目要添加到此讨论中:
在没有正确准备此类事件的情况下终止已创建 UI 控件的线程 (Thread.Abort()) 可能会导致内存按预期使用。
通过 Pinvoke 访问非托管资源而不清理它们可能会导致内存泄漏。
修改大字符串对象。不一定是内存泄漏,一旦超出范围,GC 会处理它,但是,从性能角度来看,如果经常修改大字符串,您的系统可能会受到影响,因为您不能真正依赖 GC 来确保程序的占用空间最小。
经常创建 GDI 对象来执行自定义绘图。如果经常执行 GDI 工作,请重用单个 gdi 对象。
每次调用 IDisposable 是最简单的起点,并且绝对是获取代码库中所有容易出现的内存泄漏问题的有效方法。然而,这并不总是足够的。例如,了解在运行时如何以及何时生成托管代码也很重要,并且一旦将程序集加载到应用程序域中,它们就永远不会被卸载,这会增加应用程序的占用空间。
为了防止 .NET 内存泄漏:
1) 每当创建具有“IDisposable”接口的对象时,就使用“using”构造(或“try-finally”构造)。
2) 如果类创建线程或将对象添加到静态或长期存在的集合中,则使类成为“IDisposable”。请记住,C#“事件”是一个集合。
这是一篇关于 防止内存泄漏的技巧.
您是在谈论意外的内存使用还是实际的泄漏?您列出的两个案例并不完全是泄密;在这种情况下,物体的停留时间比预期的要长。
换句话说,它们是那些称其为内存泄漏的人不知道或忘记的引用。
编辑:或者它们是垃圾收集器或非托管代码中的实际错误。
编辑2:考虑这个问题的另一种方法是始终确保对对象的外部引用得到适当释放。外部意味着代码超出您的控制范围。任何发生这种情况的情况都是可能“泄漏”内存的情况。
- 保留对不再需要的对象的引用。
回复其他评论 - 确保调用 Dispose 的一种方法是使用 using...当代码结构允许时。
让我真正意想不到的一件事是:
Region oldClip = graphics.Clip;
using (Region newClip = new Region(...))
{
graphics.Clip = newClip;
// draw something
graphics.Clip = oldClip;
}
内存泄漏在哪里?是的,你应该处置掉 oldClip
, , 也!因为 Graphics.Clip
是每次调用 getter 时都会返回新的一次性对象的罕见属性之一。
许多在非托管语言中可能导致内存泄漏的事情仍然可能在托管语言中导致内存泄漏。例如, 糟糕的缓存策略 可能会导致内存泄漏。
但正如格雷格和丹尼所说,没有完整的清单。任何可能导致在其使用寿命结束后保留内存的行为都可能导致泄漏。
死锁的线程永远不会释放根。显然,你可能会认为僵局带来了更大的问题。
死锁的终结器线程将阻止所有剩余的终结器运行,从而阻止回收所有可终结的对象(因为它们仍然以易碎列表为根)。
在多 CPU 计算机上,您创建可终结对象的速度可以比终结器线程运行终结器的速度更快。只要这种情况持续下去,你就会“泄漏”记忆。这种情况在野外发生的可能性不大,但很容易重现。
大对象堆未压缩,因此可能会因碎片而泄漏内存。
有许多对象必须手动释放。例如。远程处理没有租约和程序集的对象(必须卸载 AppDomain)。