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

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

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

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

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

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

只有一个人可以帮助你。 那个人叫Tess Ferrandez 。 (沉默)

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

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

得到这个: http : //www.red-gate.com/Products/ants_profiler/index.htm

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

  1. 将代码添加到unamanaged对象的构造函数中以记录它何时构建,并对唯一ID进行排序。 当对象再次被销毁时使用该唯一ID,并且至少可以告诉哪些对象误入歧途。
  2. 为构建新对象的每个地方grep代码; 按照该代码路径查看是否有匹配的销毁。
  3. 添加指向构造对象的链接指针,因此您可以链接到当前对象之前和之后构造的对象。 然后你可以稍后扫过它们。
  4. 添加引用计数器。
  5. 有没有“调试malloc”?

SoS (Son of Strike)中的托管调试添加function非常强大,可用于跟踪托管内存的“泄漏”,因为根据定义可以从gc根中找到它们。

它可以在WinDbg或Visual Studio中工作(尽管在WinDbg中它在很多方面都比较容易使用)

掌握它并不容易。 这是一个教程

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

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

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

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

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

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

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

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

在对非托管代码的调用周围放置了锁,并且“内存泄漏”消失了。

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

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

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

http://memprofiler.com

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

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

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

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

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

与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 /线程也必须处理,如果需要,但我把它留给了明晰)。