我看到的是真正的内存泄漏吗?

3
我正在编写一个应用程序,用户使用后可以“重置”并再次使用。实际使用会创建一些对象,包括控件。
我使用进程资源管理器检查内存使用情况,发现当应用程序被使用时,内存使用量会上升,但是当应用程序被“重置”时,内存使用量几乎不会下降。
因此,在“重置”时,我递归地Dispose所有的Control,甚至加了一个GC.Collect();。但是,Process Explorer -> (app's-) Properties -> .Net CLR Memory仍然显示堆上有很多东西和大量的内存使用。
这是否(必然)是内存泄漏?为什么强制GC没有帮助?
编辑:
我添加了GC.Collect();只是为了调试——确定是否存在泄漏。在GC起作用之前,我不担心“临时泄漏”,我只是想找出是否有永远不会超出范围的对象(尽管我还没有找到它们),或者这只是时间问题,内存将被回收。

1
你尝试过等待终结器吗?GC.WaitForPendingFinalizers() - OzrenTkalcecKrznaric
1
重用后它是否会再次上升?这可能只是一堆组件,你通过创建需要新库的一堆控件来获取它们。 - tmesser
2
听起来像是过度优化。你在描述什么问题?通常使用内存并不是一个问题。 - Erik Philips
关键问题是当您一遍又一遍地运行应用程序时,内存使用情况是否会持续增加。第一次运行期间内存使用量的增加是可以预料的,因为在幕后代码中将发生各种静态初始化操作,这些初始化操作发生在相关代码路径首次执行时。 - hmakholm left over Monica
1
@ErikPhilips 内存泄漏确实是一个问题。 - ispiro
显示剩余6条评论
3个回答

5
这是一个(必然)的内存泄漏吗?
嗯......是和不是。在C#应用程序中真正出现内存泄漏基本上是不可能的,即使一些类型在底层使用本地资源,如果没有调用Dispose(),它们也会在其终结器中清理自己。
但是,仍有可能存在失效引用和需要很长时间才能清理的本地资源(或者写得不好的本机函数将其泄漏),这与在非托管语言中真正的内存泄漏具有相同的实际结果。
强制GC为什么不起作用?
你给GC提了一个建议,但它选择不接受。 GC.Collect()并不能强制GC清理所有内容,文件说明书告诉你这一点。 其次,该内存使用情况是否真正导致问题?正在消耗多少内存? 它是否被释放? 如果只是少量内存,那么您可能在担心无关紧要的事情,请让GC完成它的工作。
没有看到您的代码,我只能猜测。 C#应用程序可以积累内存压力的几种方式。
1. 失效引用。清除不了的“死”对象引用列表。事件处理程序也会导致此类情况。例如,静态事件应始终取消注册,因为事件委托保持对订户的引用。
因此,如果该订户未取消注册到事件,而该订户又失去了范围,则基本上有一个内存泄漏,因为始终存在对这些“死”对象的有效引用。请注意,典型的事件用例不会导致此类情况发生,因为父级(事件发布者)通常在需要时失去其范围。
2. 没有释放实现IDisposable接口的对象。确实,它们可能会在它们的终结器中清理自己(所有.NET IDisposable类型都是如此,其余类型应该也是),但终结器在不确定的时间运行,使整个过程变得不可预测。尽快调用Dispose()(可能时使用using语句)。
3. 大对象堆碎片。如果分配了大量“大”对象(即> 85,000字节的对象),则此内存段可能变得分散。与在标准堆上分配的0th和1st gen对象相比,此内存不经常被清除。
.NET GC很复杂,通常非常擅长工作。有时可以接受调用GC.Collect,但您应了解何时是一个好主意,并意识到它可能会或可能不会做您想要的事情。例如,如果创建了大量短寿命对象,则有一个好的例子。

我之前在其他途径都无法解决问题时,使用了RedGate的.NET内存分析器,并且获得了良好的体验。仅供参考,我与他们没有任何工作关系或隶属关系,而且他们也提供免费试用。值得一看。


感谢您提供详细的答案。我已经编辑了问题——我正担心那些“过时引用”。 - ispiro
@ispiro:没有看到你的代码很难说,但我意识到可能太多了无法发布。你可以使用我推荐的工具,或者尝试可视化正在发生的事情(我知道,说起来容易做起来难)。只需考虑一下:引用存储在哪里(不要忘记事件/处理程序),我是否未调用实现IDisposable接口的任何对象的Dispose()方法? - Ed S.
我们使用内存转储和windbg来识别内存泄漏。windbg不太用户友好,但它是免费的,您将需要学习很多有趣的东西才能使用它。 - Puterdo Borato
@ispiro:你解决了吗?我很好奇。 - Ed S.

2

1

正如其他人所指出的那样,仅使用GC.Collect()并不能确保进行垃圾回收。你可以尝试使用GetTotalMemory方法,该方法接受一个布尔参数,允许你强制进行完整的垃圾回收,例如:

GC.GetTotalMemory(true);

这将强制进行总收集,然后给出分配的托管内存量的近似值(以字节为单位)。

与其他方法一样,此方法不能保证发生收集(请参阅MSDN文档的备注部分),而且只有符合条件的内容才能被收集。


我不明白 - 所以它是否强制进行垃圾回收,还是不强制? - ispiro
1
它表示您希望等待GC发生,但它只会等待一段时间,请参阅相关文档的备注部分。 - RobV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接