为什么.NET没有内存泄漏?

忽略不安全的代码,.NET不能有内存泄漏。 我已经从许多专家那里无休止地读到了这一点,我相信这一点。 不过,我不明白为何如此。

我的理解是,框架本身是用C ++编写的,C ++易受内存泄漏的影响。

  • 底层的框架写得如此之好,它绝对不存在内部内存泄漏的可能性?
  • 框架的代码中是否有自我pipe理甚至是治愈自己的内存泄漏?
  • 答案是我还没有考虑的其他答案吗?

这里已经有了一些很好的答案,但是我想解决一个问题。 我们再仔细看看你的具体问题:


我的理解是,框架本身是用C ++编写的,C ++易受内存泄漏的影响。

  • 底层的框架写得如此之好,它绝对不存在内部内存泄漏的可能性?
  • 框架的代码中是否有自我pipe理甚至是治愈自己的内存泄漏?
  • 答案是我还没有考虑的其他答案吗?

这里的关键是区分你的代码和他们的代码。 .Net框架(以及Java,Go,python和其他垃圾收集语言)保证,如果你依赖他们的代码, 你的代码不会泄漏内存……至less在传统意义上说。 您可能会发现自己的某些对象没有像您期望的那样被释放,但这些情况与传统的内存泄漏有细微的差别,因为对象在程序中仍然可以访问。

你很困惑,因为你正确地理解这与说你创build的任何程序根本不可能有传统的内存泄漏是不一样的。 在他们的代码中可能还存在一个泄漏内存的错误。

所以,现在你必须问自己:你宁愿相信你的代码,或者他们的代码? 请记住,他们的代码不仅受原始开发人员的testing(就像您的代码一样),而且还有成千上万(也许是数百万)其他程序员(如您自己)的日常使用。 任何重大的内存泄漏问题将被确定和纠正的第一件事情之一。 再一次,我不是说这是不可能的。 只是相信自己的代码比自己的代码更好,至less在这方面是这样。

因此,这里的正确答案是这是你的第一个build议的变种:

底层的框架写得如此之好,它绝对不存在内部内存泄漏的可能性?

这并不是说没有可能,但是比自己pipe理更安全。 我当然不知道框架中有任何已知的泄漏。

.NET 可以有内存泄漏。

垃圾收集器大都是指垃圾收集器,垃圾收集器决定何时可以摆脱一个对象(或整个对象循环)。 这避免了经典的 c和c ++风格的内存泄漏,我的意思是分配内存,而不是以后释放内存。

然而,很多时候程序员没有意识到,对象仍然有悬而未决的引用,并没有得到垃圾回收,导致…内存泄漏。

事件注册时(通常为+= ),但以后未注销,而且在访问非托pipe代码时(使用pInvokes或使用底层系统资源的对象,例如文件系统或数据库连接),并且不正确地处理资源。

在仔细阅读了Microsoft文档后,特别是“ 识别CLR中的内存泄漏 ”之后,Microsoft确实声明只要不在应用程序中实现不安全的代码,就不可能发生内存泄漏

现在,他们也指出了内存泄露的概念,或者在注释中指出了“资源泄漏”,即使用了一个对象,这个对象有一个引用不清的对象,而且处理不当。 这可能发生在IO对象,数据集,GUI元素等等。 在使用.NET时,它们就是我通常所说的“内存泄漏”,但它们不是传统意义上的泄漏。

由于垃圾收集,你不能有正规的内存泄漏(除了特殊情况,如不安全的代码和P / Invoke)。 但是,你肯定会无意中永远保留一个引用,这就有效地泄漏了内存。

编辑

我所见过的真正泄漏的最好例子是事件处理程序+ =错误。

编辑

请参阅下文,了解有关错误的解释,以及在何种情况下,它是真正的泄漏,而不是几乎真正的泄漏。

下面是.NET中的一个内存泄漏的例子,它不涉及不安全的/ pinvoke,甚至不涉及事件处理程序。

假设您正在编写一个后台服务,通过networking接收一系列消息并对其进行处理。 所以你创build一个类来保存它们。

 class Message { public Message(int id, string text) { MessageId = id; Text = text; } public int MessageId { get; private set; } public string Text { get; private set; } } 

好,那么好。 稍后,您会意识到,如果您在处理时参考了之前可用的消息,则系统中的某些要求可以变得更加容易。 可能有任何理由想要这个。

所以你添加一个新的属性…

 class Message { ... public Message PreviousMessage { get; private set; } ... } 

你写代码来设置它。 当然,在主循环的某个地方,你必须有一个variables来跟上最后的消息:

  Message lastMessageReceived; 

然后你会发现比你的服务已经被轰炸了几天,因为它已经填满了所有可用的内存和一连串过时的信息。

这里有其他内存泄漏,这家伙发现使用ant.NET Profiler: http : //www.simple-talk.com/dotnet/.net-tools/tracing-memory-leaks-in-net-applications-with-ants -profiler /

我想可以编写软件,例如.NET运行时环境(CLR),如果有足够小心的话,它不会泄漏内存。 但是,由于微软不时通过Windows更新发布.NET框架更新,所以我相当确信即使在CLR中也偶尔的错误。

所有软件都可能泄漏内存。

但正如其他人已经指出的,还有其他种类的内存泄漏。 虽然垃圾回收器负责处理“经典”内存泄漏,但仍然存在释放所谓的非托pipe资源 (如数据库连接,打开文件,GUI元素等)的问题。 这就是IDisposable接口进来的地方。

另外,我最近在.NET-COM互操作设置中遇到了可能的内存泄漏问题。 COM组件使用引用计数来决定何时可以释放它们。 .NET增加了另一个引用计数机制,可以通过静态的System.Runtime.InteropServices.Marshal类来影响这个机制。

毕竟,即使在.NET程序中,您仍然需要小心资源pipe理。

.NET代码中绝对可以有内存泄漏。 在某些情况下,某些对象会自行创build(尽pipe这些对象通常是IDisposable )。 在这种情况下,如果无法在对象上调用Dispose() ,则绝对会导致实际的C / C ++样式的内存泄漏,而您无法引用已分配的对象。

在某些情况下,某些计时器类可以有这种行为,例如。

任何情况下,如果你有一个可能重新安排自己的asynchronous操作,你可能会泄漏。 asynchronous操作通常将根callback对象,防止集合。 在执行期间,对象被执行线程扎根,然后新调度的操作重新build立对象的根。

这里是一些使用System.Threading.Timer的示例代码。

 public class Test { static public int Main(string[] args) { MakeFoo(); GC.Collect(); GC.Collect(); GC.Collect(); System.Console.ReadKey(); return 0; } private static void MakeFoo() { Leaker l = new Leaker(); } } internal class Leaker { private Timer t; public Leaker() { t = new Timer(callback); t.Change(1000, 0); } private void callback(object state) { System.Console.WriteLine("Still alive!"); t.Change(1000, 0); } } 

就像GlaDOS一样, Leaker对象将无限期地“活着” – 但是,没有办法访问这个对象(除了内部,对象怎么知道什么时候不再被引用呢?)

如果你不是指使用 .NET的应用程序,这些答案讨论得非常好,但实际上是指运行时本身,那么它在技术上可能会有内存泄漏,但在这一点上,垃圾收集器的实现可能几乎是bug -自由。 我曾经听说过一个在运行时发现错误的情况,或者是在标准库中发生内存泄漏的情况。 但是我不记得那是什么东西(非常模糊的东西),我不认为我能够再次find它。

那么.NET有一个垃圾收集器 ,当它看起来合适的清理东西。 这是与其他非托pipe语言分开的。

但.NET可以有内存泄漏。 例如,GDI泄漏在Windows窗体应用程序中很常见。 其中一个应用程序,我帮助定期发展经验。 而当办公室的员工整天使用它的多个实例时,他们达到Windows固有的10000个GDI对象限制并不罕见。

在.NET中不存在的C / C ++内存泄漏的一个主要来源是什么时候释放共享内存

以下内容来自于devise.NET类库的Brad Abrams领导的类

“嗯,第一点当然是没有内存泄漏,对吧?不是?还有内存泄漏?好吧,还有一种不同types的内存泄漏。那怎么样?所以那种内存泄漏,我们在旧世界里,没有一个是你曾经习惯于记忆的东西,然后忘记做一个自由的或者是增加的参考,而忘记做一个释放,或者是任何一个对,在新的世界里,垃圾收集器最终拥有所有的内存,当垃圾收集器不再有任何引用时,垃圾收集器将释放这些东西,但是仍然可能有泄漏,对吗?如果你保留对这个对象的引用那么垃圾收集器就不能释放这些东西,所以很多时候,你会认为你已经摆脱了整个对象的graphics,但是仍然有一个人持有一个参考,然后,垃圾收集器不能释放,直到你删除所有的引用。

另一个,我认为是一个大问题。 没有内存所有权问题。 如果你阅读WIN32 API文档,你会看到,好的,我首先分配这个结构,然后将其传入,然后将其填充,然后释放它。 或者我告诉你这个大小,你分配它,然后我释放它,或者你知道,所有这些辩论都是关于谁拥有这个内存以及它应该被释放的地方。 很多时候,开发人员只是放弃这一点,然后说:“好吧,无论如何。 那么当应用程序closures时它就会自由了,“这不是一个好的计划。

在我们的世界里,垃圾收集器拥有所有的托pipe内存,因此不存在内存所有权问题,无论您是否创build它并将其传递给应用程序,应用程序创build并开始使用它。 没有任何问题,因为没有歧义。 垃圾收集器拥有这一切。 “

完整的脚本

请记住,caching和内存泄漏之间的区别是策略。 如果你的caching有一个不好的策略(或更糟糕的是没有)去除对象,这是无法区分内存泄漏。

这个参考展示了如何在.Net中使用弱事件模式发生泄漏。 http://msdn.microsoft.com/en-us/library/aa970850.aspx

.NET可以有内存泄漏,但它可以帮助您避免它们。 所有引用types的对象都是从托pipe堆中分配的,这个堆跟踪当前正在使用哪些对象(值types通常在堆栈上分配)。 每当在.NET中创build一个新的引用types对象时,就从这个托pipe堆中分配它。 垃圾收集器负责定期运行并释放不再使用的对象(不再被应用程序中的其他任何东西引用)。

Jeffrey Richter 通过C#编写的CLR书很好地介绍了如何在.NET中pipe理内存。

我发现的最好的例子实际上来自Java,但同样的原则适用于C#。

我们正在阅读由许多长行组成的文本文件(每行是堆中的几MB)。 从每个文件中,我们search了几个关键的子string,只保留了子string。 处理了几百个文本文件后,我们的内存不足了。

事实certificate,string.substring(…)将保持对原始长string的引用…即使我们只保留1000个左右的字符,这些子string仍然会使用几个MB的内存。 实际上,我们将每个文件的内容保存在内存中。

这是导致泄漏内存的一个悬而未决的参考的例子。 子string方法试图重用对象,但最终浪费了内存。

编辑:不知道这个具体的问题困扰.NET。 这个想法是为了说明以垃圾收集语言进行的实际devise/优化,在大多数情况下,这种语言是聪明而有用的,但是会导致不必要的内存使用。

如果你正在使用一个托pipe的DLL,但该DLL contians不安全的代码呢? 我知道这是分裂的头发,但如果你没有源代码,那么从你的观点来看,你只使用托pipe代码,但你仍然可以泄漏。