哪些策略和工具可用于发现.NET中的内存泄漏?
我写了10年的C ++。我遇到了内存问题,但是可以通过合理的努力来解决它们。
在过去的几年中,我一直在编写C#。我发现我仍然遇到很多内存问题。由于不确定性,它们很难诊断和修复,并且因为哲学是,当我们一定要这样做时,我们不必担心这些事情。
我发现的一个特殊问题是,我必须明确地处置和清理代码中的所有内容。如果我不这样做,那么内存探查器并没有真正的帮助,因为有太多杂乱无章的东西浮在水面上,我们无法在他们试图向我们展示的所有数据中找到泄漏。我想知道我是否有错误的主意,或者我拥有的工具不是最好的。
什么样的策略和工具对于解决.NET中的内存泄漏很有用?
解决方案
当我怀疑内存泄漏时,我使用Scitech的MemProfiler。
到目前为止,我发现它非常可靠且功能强大。它至少一次拯救了我的培根。
GC在.NET IMO中可以很好地工作,但是就像其他任何语言或者平台一样,如果编写不好的代码,也会发生不好的事情。
除非应用程序很琐碎,否则在编写托管代码时仍然需要担心内存问题。我将建议两件事:首先,通过C读取CLR,因为它将了解.NET中的内存管理。其次,学习使用像CLRProfiler(Microsoft)这样的工具。这可以让我们了解导致内存泄漏的原因(例如,我们可以查看大对象堆碎片)
我们在项目中使用了Red Gate软件的Ants Profiler Pro。它对于所有基于.NET语言的应用程序都非常有效。
我们发现.NET垃圾收集器清理内存中的对象(应该是)非常"安全"。它会保留对象,只是因为我们将来可能会使用它。这意味着我们需要更加注意在内存中膨胀的对象的数量。最后,我们将所有数据对象转换为"按需扩展"(就在请求字段之前),以减少内存开销并提高性能。
编辑:这是对我所说的"按需膨胀"的进一步解释。在数据库的对象模型中,我们使用父对象的属性来公开子对象。例如,如果我们有一些记录以一对一的方式引用了其他"详细"或者"查找"记录,那么我们将这样构造它:
class ParentObject Private mRelatedObject as New CRelatedObject public Readonly property RelatedObject() as CRelatedObject get mRelatedObject.getWithID(RelatedObjectID) return mRelatedObject end get end property End class
我们发现,当内存中有很多记录时,上述系统会产生一些实际的内存和性能问题。因此,我们切换到一个仅在对象被请求时就对其进行膨胀的系统,而数据库调用仅在必要时进行:
class ParentObject Private mRelatedObject as CRelatedObject Public ReadOnly Property RelatedObject() as CRelatedObject Get If mRelatedObject is Nothing mRelatedObject = New CRelatedObject End If If mRelatedObject.isEmptyObject mRelatedObject.getWithID(RelatedObjectID) End If return mRelatedObject end get end Property end class
事实证明,这样做的效率要高得多,因为对象一直保留在内存中,直到需要它们为止(访问Get方法)。它在限制数据库命中方面提供了极大的性能提升,并在内存空间上获得了巨大的收益。
要记住的最好的事情是跟踪对对象的引用。以挂起对我们不再关心的对象的引用结束是非常容易的。
如果我们不再使用某些东西,请摆脱它。
习惯于使用具有过期期限的缓存提供程序,这样,如果在期望的时间范围内未引用某些内容,则将其取消引用并清除。但是,如果要对其进行大量访问,它将在内存中显示。
最好的工具之一是使用Windows调试工具,并使用adplus获取进程的内存转储,然后使用windbg和sos插件分析进程的内存,线程和调用堆栈。
我们也可以使用此方法来确定服务器上的问题,在安装工具之后,共享目录,然后使用(网络使用)从服务器连接到共享,并崩溃或者挂起该过程。
然后离线分析。
如果我们观察到的泄漏是由于缓存实施失控造成的,那么在这种情况下,我们可能需要考虑使用WeakReference。这可以帮助确保在必要时释放内存。
但是,恕我直言,最好在我们真正知道需要将对象保留多长时间的情况下考虑使用定制的解决方案,因此,根据情况设计适当的内务处理代码通常是最好的方法。
只是为了解决遗忘问题,请尝试此博客文章中描述的解决方案。这是本质:
public void Dispose () { // Dispose logic here ... // It's a bad error if someone forgets to call Dispose, // so in Debug builds, we put a finalizer in to detect // the error. If Dispose is called, we suppress the // finalizer. #if DEBUG GC.SuppressFinalize(this); #endif } #if DEBUG ~TimedLock() { // If this finalizer runs, someone somewhere failed to // call Dispose, which means we've failed to leave // a monitor! System.Diagnostics.Debug.Fail("Undisposed lock"); } #endif
我们正在使用非托管代码吗?根据Microsoft的说法,如果我们不使用非托管代码,则传统意义上的内存泄漏是不可能的。
但是,可能不会释放应用程序使用的内存,因此应用程序的内存分配可能会在应用程序的整个生命周期中增长。
From How to identify memory leaks in the common language runtime at Microsoft.com A memory leak can occur in a .NET Framework application when you use unmanaged code as part of the application. This unmanaged code can leak memory, and the .NET Framework runtime cannot address that problem. Additionally, a project may only appear to have a memory leak. This condition can occur if many large objects (such as DataTable objects) are declared and then added to a collection (such as a DataSet). The resources that these objects own may never be released, and the resources are left alive for the whole run of the program. This appears to be a leak, but actually it is just a symptom of the way that memory is being allocated in the program.
为了处理此类问题,我们可以实现IDisposable。如果我们想了解一些处理内存管理的策略,我建议我们搜索IDisposable,XNA,内存管理,因为游戏开发人员需要具有更多可预测的垃圾收集,因此必须强制GC来做。
一个常见的错误是不删除订阅对象的事件处理程序。事件处理程序订阅将防止对象被回收。另外,请查看using语句,该语句使我们可以为资源的生存期创建有限的范围。
适用于Windows的大枪调试工具
这是一组了不起的工具。我们可以使用它分析托管堆和非托管堆,也可以离线进行分析。这对于调试我们的ASP.NET应用程序之一非常方便,该应用程序由于内存过度使用而不断回收。我只需要创建在生产服务器上运行的活动进程的完整内存转储,所有分析都在WinDbg中脱机完成。 (事实证明,一些开发人员过度使用了内存中会话存储。)
博客上有很多关于该主题的有用文章:"如果被破坏了……"。
该博客使用windbg和其他工具进行了一些非常精彩的演练,以跟踪所有类型的内存泄漏。优秀的阅读能力可以培养技能。