当你完成所有错误时,追踪内存泄漏的策略

时间:2009-03-01 23:06:13

标签: c# memory-leaks

我的程序,唉,某处有内存泄漏,但如果我知道它是什么,我会被诅咒。

它的工作是读取一堆~2MB的文件,进行一些解析和字符串替换,然后以各种格式输出它们。当然,这意味着很多字符串,因此进行内存跟踪表明我有很多字符串,这正是我所期望的。程序的结构是一系列类(每个都在它们自己的线程中,因为我是 idiot ),它们作用于代表内存中每个文件的对象。 (每个对象都有一个输入队列,两端都使用锁。虽然这意味着我可以并行运行这个简单的处理,但这也意味着我有多个2MB对象位于内存中。)每个对象的结构都是由一个模式对象定义的。

我的处理类在完成处理时引发事件,并传递对包含所有字符串的大对象的引用,以将其添加到下一个处理对象的队列中。使用函数调用替换事件以添加到队列不会阻止泄漏。其中一种输出格式要求我使用非托管对象。在类上实现Dispose()不会阻止泄漏。我已使用索引名称替换了对架构对象的所有引用。没有骰子。我不知道是什么导致它,不知道在哪里看。内存跟踪没有帮助,因为我看到的是一堆正在创建的字符串,我没有看到引用在内存中的位置。

我们现在几乎要放弃并回滚,但我有一个病态的需要,确切地知道我是如何弄乱它的。我知道Stack Overflow无法完全梳理我的代码,但您可以建议哪些策略来跟踪此泄漏情况?我可能会在自己的时间里这样做,所以任何方法都是可行的。

13 个答案:

答案 0 :(得分:11)

我尝试的一种技术是系统地减少演示问题所需的代码量,而不会使问题消失。这被非正式地称为“分而治之”,是一种强大的调试技术。一旦你有一个演示同样问题的 small 示例,你就会更容易理解。也许记忆问题会在那时变得更加清晰。

答案 1 :(得分:5)

只有一个人可以帮助你。那个人的名字是Tess Ferrandez。 (沉默)

但是说真的。阅读她的博客(第一篇文章非常贴切)。看看她如何调试这些东西,可以让你深入了解你的问题是什么。

答案 2 :(得分:2)

我喜欢微软的CLR Profiler。它提供了一些很好的工具,用于可视化托管堆和跟踪泄漏。

答案 3 :(得分:1)

我使用dotTrace分析器来跟踪内存泄漏。它比方法论的试验和错误更具确定性,并且可以更快地提高结果。

对于系统执行的任何操作,我拍摄快照然后运行该函数的几次迭代,然后拍摄另一个快照。比较两者将显示在两者之间创建但未释放的所有对象。然后,您可以在创建时查看堆栈框架,从而确定哪些实例未被释放。

答案 4 :(得分:1)

获取此信息:http://www.red-gate.com/Products/ants_profiler/index.htm

内存和性能分析非常棒。能够实际看到正确的数字而不是猜测使得优化非常快。我已经在工作中使用它来减少主应用程序的内存占用。

答案 5 :(得分:0)

  1. 将代码添加到。的构造函数中 unamanaged对象记录它的时间 onstructed,并排序一个唯一的ID。 在对象时使用该唯一ID 再次被摧毁,你可以在 至少告诉哪些人要去 引入歧途。
  2. 为每个地方提供Grep代码 构造一个新的对象;跟着那个 代码路径,看看你是否有 匹配销毁。
  3. 添加链接指针 构造对象,所以你有一个 链接到构造的对象 在当前之前和之后。然后你可以稍后扫过它们。
  4. 添加参考计数器。
  5. 是否有“debug malloc”?

答案 6 :(得分:0)

管理调试添加SoS(罢工之子)非常强大,能够追踪管理内存的“漏洞”,因为根据定义可以从gc根中找到它们。

它可以在WinDbg或Visual Studio中工作(虽然它在WinDbg中很容易使用)

掌握这一点并不容易。这是tutorial

我也会推荐查看Tess Fernandez的博客。

答案 7 :(得分:0)

你怎么知道你实际上有内存泄漏的事实?

另一件事:您写道您的处理类正在使用事件。如果您已经注册了一个事件处理程序,它将使拥有该事件的对象保持活动状态 - 即GC无法收集它。如果您希望对象被垃圾收集,请确保取消注册所有事件处理程序。

答案 8 :(得分:0)

如果您的非托管对象确实是泄漏的原因,您可能希望在AddMemoryPressure / RemoveMemoryPressure分配非托管内存和Finalize时调用Dispose /它在哪里释放非托管内存。这将使GC更好地处理这种情况,因为它可能没有意识到需要安排收集。

答案 9 :(得分:0)

小心如何定义“泄漏”。 “使用更多内存”甚至“使用太多内存”与“内存泄漏”不同。在垃圾收集环境中尤其如此。可能只是GC不需要收集你看到的额外内存。还要注意虚拟内存使用和物理内存使用之间的区别。

最后并非所有“内存泄漏”都是由“内存”类型的问题引起的。我曾被告知(未被要求)修复导致IIS频繁重启的紧急内存泄漏。事实上,我进行了分析,发现我在StringBuilder类中使用了很多字符串。我为StringBuilders实现了一个对象池(来自MSDN文章),并且内存使用量大幅下降。

IIS仍然经常重启。这是因为没有内存泄漏。相反,有非托管代码声称是线程安全但不是。在Web服务(多个线程)中使用它会导致它在整个C运行时库堆中写入。由于没有人在寻找非托管异常,所以没有人看到这一点,直到我碰巧使用自动化QA对AQtime进行了一些分析。碰巧有一个事件窗口,恰好显示了来自C运行时库的痛苦声。

锁定了对非托管代码的调用,“内存泄漏”消失了。

答案 10 :(得分:0)

您提到过您使用的活动。当您完成对象时,是否从这些事件中删除处理程序?我发现,如果你添加了一堆处理程序而没有删除它们,那么'松散'事件处理程序会导致很多内存泄漏问题。

答案 11 :(得分:0)

.Net的最佳内存分析工具是:

http://memprofiler.com

此外,虽然我在这里,但.Net的最佳性能分析器是:

http://www.yourkit.com/dotnet/download/index.jsp

它们也物有所值,开销低且易于使用。任何认真对待.Net开发的人都应该考虑将这两者作为个人投资并立即购买。他们都有免费试用。

我使用C#编写了超过70万行代码的实时游戏引擎,并使用这两种工具花费了数百小时。我从2002年开始使用Sci Tech产品和YourKit!过去三年。虽然我已经尝试了其他一些我总是回到这些。

恕我直言,他们都非常出色。

答案 12 :(得分:0)

与Charlie Martin相似,您可以这样做:

static unigned __int64 _foo_id = 0;
foo::foo()
{
    ++_foo_id;
    if (_foo_id == MAGIC_BAD_ALLOC_ID)
        DebugBreak();
    std::werr << L"foo::foo @ " << _foo_id << std::endl;
}
foo::~foo()
{
    --_foo_id;
    std::werr << L"foo::~foo @ " << _foo_id << std::endl;
}

如果你可以重新创建它,即使是一次或两次具有相同的分配ID,这也可以让你看看当时和那里发生了什么(显然TLS /线程也必须处理,如果需要,但我离开了为了清楚起见)。