混合模式 C++/CLR 应用程序中的内存泄漏问题

10
我在我的混合模式C++/CLR .NET应用程序中遇到了一个缓慢的内存泄漏问题。(这是将C++本机静态库链接到带有"/clr"编译器设置的VS2008 C++/CLR Windows窗体应用程序中)
典型行为: 应用程序开始使用30 MB (私有内存)。然后在模拟重负载下每小时泄漏一些内存,比如说1 MB。这模拟应用程序在线运行数天或数周。
我尝试使用几个工具来跟踪内存泄漏,包括附带Visual Studio CRT库的CRT调试工具。我还使用了一个商业的泄漏检测工具("Memory Validator")。
两者都报告在关闭时有可忽略的内存泄漏(有几个次要条目,加起来只有几KB,我不担心)。此外,在运行时,我可以看到跟踪的内存似乎不那么多(所以我不认为它只是保持并在应用程序退出时释放的内存)。我得到了约5MB的列出内存(总共> 30MB)。
该工具(Memory Validator)被设置为跟踪所有内存使用情况(包括malloc、new、虚拟内存分配和许多其他类型的内存分配)。基本上,已经选择了跟踪哪些内存的每个设置。
.NET映像报告它正在使用约1.5 MB的内存(来自perfmon)。
这是最后一个信息: 我们有一个版本的应用程序,它作为本机控制台应用程序运行(完全本机 - 没有CLR)。这与混合模式相比仅缺少UI部分。这似乎根本不会泄漏内存,并且峰值为约5MB的私有字节。
因此,我想在这里传达的基本上是,我不认为任何本机代码都会泄漏内存。
还有一个谜团: 我发现这篇文章提到了针对2.0框架(我正在使用)的混合模式应用程序中的内存泄漏问题: http://support.microsoft.com/kb/961870

很不幸,细节非常少,所以我不确定它是否相关。我尝试将目标从2.0改为3.5框架,但仍然遇到相同的问题(也许我没有做对)。

有人有任何建议吗?

以下几点可能对我有所帮助:

  • 还有其他我没有追踪的内存分配方式吗?
  • 为什么数字不符合?我得到了5 MB的CRT内存使用量,1.5 MB的.NET内存使用量,那么为什么整个应用程序使用30MB的私有字节?这全部都与.NET框架有关吗?为什么泄漏工具中看不到它们?.NET框架不会出现在某种分配的内存中吗?
  • 还有其他能够与混合模式应用程序良好配合的泄漏检测工具吗?

感谢您的任何帮助。

约翰


.NET 2.0 和 .NET 3.5 都使用相同的 CLR 2.0,正如您可以从版本号轻松看出的那样;-) - Christian Klauser
5个回答

7

好的,我终于找到了问题所在。

这是由于 /EH(异常处理)设置不正确造成的。

基本上,在混合模式 .NET 应用程序中,您需要确保所有静态链接库都使用 /EHa 编译,而不是默认的 /EHs。

(应用程序本身也必须使用 /EHa 编译,但这是一个已知的问题 - 如果您不使用它,编译器将报告错误。问题是当您链接其他静态本机库时。)

问题在于,在使用 /EHs 编译的本机库中抛出的在应用程序的托管部分中捕获的异常最终无法正确处理。C++ 对象的析构函数也无法正确调用。

在我的情况下,这只发生在一个罕见的地方,因此我花了很长时间才发现。


4

就像Spence所说的那样,但是针对C++/CLI ;)....

对于在C++/CLI中使用的任何对象,如果您从C++代码中创建了更多的该对象,则应尝试使用堆栈分配语义,即使这是一种编译器魔术,它也能够设置嵌套的__try {} __finally {}语句,您可能已经习惯了使用本机代码(即以不丢失对Dispose的调用的方式进行设置)。

Nish's code project上的文章 关于C++/CLI堆栈分配语义非常好,并深入介绍了如何模拟使用{}。

您还应确保删除任何实现IDisposable的对象,因为您无法在C++/CLI中调用Dispose,如果您没有使用堆栈语义,则delete会为您完成此操作。

我通常会自己调用流的Close并在完成对象时尝试分配nullptr,以防万一。

如果您正在为对象分配事件,您可能会泄漏内存。您可能还想查看关于内存问题的这篇文章,特别是有关事件订阅者的部分。

作为最后的手段(或者也许是第一步:),我过去做过的一件事是利用CLR分析器API,这里是另一篇文章,介绍了如何做到这一点,作者Jay Hilyard提供了一个回答:

  • 使用了多少.NET类型,每个类型的实例数量有多少?
  • 每种类型的实例有多大?
  • 垃圾回收提供了哪些通知,并且您可以找出什么?
  • 垃圾回收何时收集对象实例?

我注意到,与某些商品分析工具相比,它可以让您更好地了解情况,但根据您的分配配置文件,它们有时可能会误导(顺便说一句,要注意大对象堆问题,> ~83kb对象是特别处理的,在这种情况下,我建议离开大对象堆:)。

考虑到您的评论,还有一些事情...

我之前发过帖子,关于图像加载不会计入配额或任何其他可辨别的统计数据,这意味着您可能需要追踪一些句柄或加载器问题(请参见加载器锁定),但在此之前,您可以尝试设置一些约束执行区域,它们可以起到奇效,但很遗憾难以适应非纯净代码。

这篇最新的MSDN Mag文章记录了许多类似于perfmon的内存探测(此旧文章的后续)。

VS Perf Blog中可以学习如何在Visual Studio中使用SOS来追踪有问题的DLL,这可能会很方便。相关帖子也很不错。

Maoni Stephen's Blogcompany,他说自己在性能团队,但实际上他所有的帖子都与GC有关,以至于他几乎可以写一本关于它的书。

Rick Byers是CLR诊断团队的开发人员,他的许多博客朋友也是很好的信息源。然而,我强烈建议还参考相当新的dev/diagnostics forum。他们最近扩大了讨论范围。

代码覆盖工具跟踪通常可以帮助您,为您提供实际运行情况的概述。

特别是,这些特定统计数据可能无法为您提供全局视图,我可以说,最近我发现(即使是使用.net4beta二进制文件),这家公司的分析器非常好,它能够从其分析跟踪中推导出本地/托管泄漏,并将您带回到确切的源代码行(即使进行了优化,也非常好(而且它有30天的试用期))。

祝你好运!!希望其中一些对你有所帮助,因为我现在正在做同样的工作 ;)


谢谢 - 这里有一些好的评论。事实是:无论是“# Total committed Bytes”还是“.NET性能对象中的大对象堆大小”,似乎都没有显示出.NET对象泄漏。我对本地代码中调试内存泄漏比CLR更熟悉,所以也许我对这些数字的含义有误解。你能否纠正我? CLR内存泄漏是否会在上述性能对象中显示? - John

0
尝试使用:DebugDiags
生成一些内存转储后,它会为您提供一个很好的摘要,显示哪些内存已分配,并且根据找到的PDB,可以告诉您是由谁分配的。

0

你可能存在引用泄漏问题,可以尝试使用ANTS性能分析软件进行调查。Ants Profiler

引用泄漏是.NET中的内存泄漏问题,当你持有一个对象的引用时,它就无法被垃圾回收,从而导致你的内存使用量不断上升。


但是这不会显示在“.NET内存中所有堆的字节数”值中吗?我一直在使用Process Explorer观察它,它没有泄漏。泄漏出现在私有字节中,但不在“所有堆中的字节数”中。 - John

0

你可能错过了一些处理器,如果你正在使用GDI+和许多其他API,这种情况可能会发生。

如果你运行静态分析工具FXCop,它有一个规则可以检查是否在提供接口的对象上调用了dispose(或使用了“using”语句)。在.NET中,如果函数使用非托管代码,通常会为你提供一个dispose或close方法,以防资源/内存泄漏。


很好的想法。我猜这些泄漏不会出现在CLR内存使用情况中,因为它们是本地的,但也不会出现在本地泄漏检测工具中,因为它们不是我的代码。我会试一试(虽然我不知道FXCop能否处理混合模式的C++/CLI)。 - John
希望能有所帮助。我只是瞎猜,但我曾在一个使用GDI的应用程序中遇到过这种情况,我发现几乎每个GDI+对象都需要一个释放器。请注意,这是在“using”出现之前的时代。 - Spence

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