关于.NET中垃圾收集器的问题(内存泄漏)

时间:2009-11-13 21:52:37

标签: c# .net c++ garbage-collection

我想这是非常基本的,但由于我自己学习.NET,我不得不问这个问题。

我习惯用C编码,你需要free()一切。在C ++ / .NET中,我已经阅读了有关垃圾收集器的内容。根据我的理解,当一个实例不再使用时(在对象的范围内),它被垃圾收集器释放。

所以,考虑到这一点,我构建了一个小测试应用程序。但是,似乎我没有得到什么,因为在做同样的事情几次(比如,打开一个表格,关闭它,重新打开它等),内存泄漏。而且时间很长。

我试着在Google上查看这些内容,但我没有找到任何适合初学者的内容。

  1. 垃圾收集器在不再使用时是否真正释放了每个对象,或者我必须处理异常?我错过了什么?
  2. 是否有免费工具可以查找内存泄漏?

7 个答案:

答案 0 :(得分:11)

是的,当垃圾收集器不再使用时,垃圾收集器会释放它们。

我们通常称.NET中的内存泄漏更像是:

  • 您正在使用外部资源(不是垃圾收集)而忘记释放它们。这通常通过实现IDisposable接口来解决。
  • 您认为没有对某个给定对象的引用,但确实存在某个地方而且您不再使用它们,但垃圾收集器并不知道它们。

此外,只在需要时回收内存,这意味着垃圾收集器在给定时间激活并执行一个集合,确定它们可以释放哪些内存并释放它。在此之前,内存未被声明,因此可能看起来内存正在丢失。

在这里,我想我会提供一个更复杂的答案,只是为了澄清。

首先,垃圾收集器在自己的线程中运行。你必须明白,为了回收内存,垃圾收集器需要停止所有其他线程,以便他可以跟进引用链,确定什么取决于什么。这就是内存没有立即释放的原因,垃圾收集器意味着某些性能成本。

垃圾收集器在内部管理内存。以非常简单的方式,对于长寿命,短寿命和大尺寸物体,有几代。垃圾收集器每次执行收集时都会将对象从一代移动到另一代,这种收集更常发生在短期生成中,对于长期存在的生成。它还重新分配对象以获得最可能的连续空间,因此执行集合是一个代价高昂的过程。

如果你真的想看到释放表单的效果(当超出范围并且没有更多参考时),你可以致电GC.Collect()。这样做仅仅是为了测试,调用Collect是非常不明智的,除了极少数情况下你确切知道你正在做什么以及它将产生的影响。

稍微解释一下IDispose接口的Dispose方法。

Dispose不是通常的C ++方式的析构函数,它根本不是析构函数。 Dispose是一种确定性地摆脱非托管对象的方法。示例:假设您调用的外部COM库恰好分配1GB内存,因为它正在做什么。如果没有Dispose,那么内存就会停留在那里,浪费空间直到GC进入集合并通过调用实际的对象析构函数来回收非托管内存。因此,如果你想立即释放内存,你必须调用Dispose方法,但你并没有“被迫”这样做。

如果您不使用IDisposable接口,那么您必须释放Finalize方法中的非托管资源。当GC尝试回收对象时,会自动从对象析构函数调用Finalize。因此,如果你有一个正确的finalize方法,非托管内存将被释放。调用Dispose只会使它具有确定性。

答案 1 :(得分:8)

是什么导致您得出内存泄漏的结论?在垃圾收集下,无法保证内存立即释放,并且通常GC在您的进程内存达到某个阈值或可用堆已用尽之前不会启动。 (确切的启发式方法很复杂而且不重要。)因此,您的进程内存上升但不会下降这一事实并不一定意味着存在错误。可能只是因为GC还没有开始清理你的过程。

此外,您确定是否没有对您的对象的引用?你可能有你不知道的参考文献。 .NET应用程序中的大多数内存泄漏都是因为人们没有意识到他们的内存仍在某处被引用。

答案 2 :(得分:7)

任务管理器是检查内存使用情况的可怕方法。如果要研究垃圾收集器的工作原理,请安装CLR Profiler并使用它来分析您的应用程序。它会告诉你垃圾收集器究竟在做什么。

请参阅How To: Use CLR Profiler

答案 3 :(得分:4)

我将此作为答案而不是对问题发表评论,但这是OP在评论中提出的问题: MSDN上的using声明。 MSDN上的IDisposable界面。

这就是问题的症结所在:对于对象析构函数你已经习惯了。你被告知如何正确编码的一切都是从你的潜意识中尖叫起来,说这不可能是真的,如果是真的,这是错误的和可怕的。这是非常不同的;很难记住我一开始真的很鄙视它(我是一个自豪的C ++开发人员)。

我个人向你保证:一切都会好的!

这是另一件好事: Destructors and Finalizers in Visual C++

答案 4 :(得分:3)

当对象不再被引用时,GC实际上不一定会释放东西。 GC将在未来的某个时间收集它 - 您不确切知道何时,但如果需要内存,GC将在必要时执行收集。

答案 5 :(得分:1)

如果您只是想弄清楚是否有内存泄漏,请查看随您的Windows副本附带的perfmon

特别是.NET CLR内存计数器bytes in all heaps,如果此数字稳步增长,则会出现泄漏。

您甚至可以通过将第2代堆大小与大对象堆大小进行比较来深入挖掘。如果前者稳步增长,则会出现大量数据泄露。

确认存在泄漏后,您可以使用windbg附加并使用sos extensions确定泄漏的内容。

如果你有能力花几块钱看看.NET Memory Profiler

答案 6 :(得分:0)

可以使用免费工具查看.Net中的托管堆,使用SOSEXWinDBGSOS扩展,可以运行程序,暂停,然后查看堆栈中存在哪些对象(更重要的是)哪些其他对象持有对它们的引用(根),这将阻止GC收集它们。