【问题标题】:Memory leak in Mixed Mode C++/CLR application混合模式 C++/CLR 应用程序中的内存泄漏
【发布时间】:2010-11-08 12:36:57
【问题描述】:

我在混合模式 C++/CLR .NET 应用程序中遇到缓慢的内存泄漏问题。

(它是使用“/clr”编译器设置链接到 VS2008 C++/CLR Windows 窗体应用程序的 C++ 原生静态库)

典型行为:应用开始使用 30 MB(私有内存)。然后缓慢地泄漏内存,例如在模拟重负载下运行时每小时 1 MB。这会模拟应用运行数天或数周。

我尝试使用多种工具来追踪内存泄漏,包括 Visual Studio CRT 库附带的 CRT 调试工具。我还使用了商业泄漏检测工具(“Memory Validator”)。

两者都报告了在关机时可忽略不计的内存泄漏(我不担心的几个小条目,总计几 KB)。此外,我可以在运行时看到跟踪的内存似乎并没有那么多(所以我不相信它只是被持有的内存并且只在应用程序退出时释放)。我获得了大约 5 MB 的列出内存(总共 > 30 MB)。

该工具(Memory Validator)用于跟踪所有内存使用情况(包括 malloc、new、虚拟内存分配和一大堆其他类型的内存分配)。基本上,所有要跟踪的内存设置都已选择。

.NET 映像报告它正在使用大约 1.5 MB 的内存(来自 perfmon)。

这是最后一点信息:我们有一个应用程序版本,它作为本机控制台应用程序运行(纯本机 - 根本不是 CLR)。这与混合模式 95% 相同,但没有 UI 内容。这似乎根本没有泄漏内存,峰值约为 5MB 私有字节。

所以基本上我在这里想要表达的是,我认为没有任何本机代码正在泄漏内存。

另一个难题:我发现这指的是针对 2.0 框架(我是)时混合模式应用程序中的内存泄漏:http://support.microsoft.com/kb/961870

不幸的是,细节太少了,所以我不确定它是否相关。我确实尝试针对 3.5 框架而不是 2.0,但仍然遇到同样的问题(也许我没有做对)。

有人有什么建议吗?

一些可能对我有帮助的事情:

  • 是否还有其他类型的内存分配我没有跟踪?
  • 为什么数字不加起来?我得到了 5 MB 的 CRT 内存使用量,1.5 MB 的 .NET 内存,那么整个应用程序怎么会使用 30MB 的私有字节呢?这一切都与.NET 框架有关吗?为什么我在泄漏工具中看不到这些? .NET 框架不会显示为某种分配的内存吗?
  • 任何其他泄漏检测工具可以很好地与混合模式应用程序配合使用?

感谢您的帮助

约翰

【问题讨论】:

  • .NET 2.0 和 .NET 3.5 都使用相同的 CLR 2.0,您可以从版本号中轻松看出 ;-)

标签: .net c++ memory-leaks mixed-mode


【解决方案1】:

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

这是由 /EH(异常处理)的错误设置引起的。

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

(应用程序本身也必须使用 /EHa 编译,但这是给定的 - 如果您不使用它,编译器会报告错误。问题是当您链接到其他静态原生库时。)

问题在于,在应用程序的托管位中捕获的异常是在使用 /EHs 编译的本机库中引发的,最终无法正确处理异常。然后没有正确调用 C++ 对象的析构函数。

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

【讨论】:

    【解决方案2】:

    就像 Spence 所说,但对于 C++/CLI ;)....

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

    Nish'sarticle at the code project here 在 C++/CLI 堆栈分配语义上非常好,并且深入探讨了如何使用{} 进行模拟。

    您还应该确保删除任何实现 IDisposable 的对象,因为您不能在 C++/CLI 中调用 Dispose,如果您不使用堆栈语义,请删除为您执行此操作..

    我通常在 Streams 上调用 Close 并在我完成对象时尝试分配 nullptr,以防万一。

    您可能还想查看this article on memory issues,特别是关于事件订阅者,如果您将事件分配给您的对象,您可能会泄漏...

    作为最后的手段(或者可能是第一个:),我过去做过的一件事是使用 CLR 分析器 API,here's another article 关于如何做到这一点,作者的作者(Jay Hilyard)有一个例子答案;

    • 在使用的每种 .NET 类型中,如何 许多对象实例正在 分配?
    • 实例有多大 每种类型?
    • 什么通知 GC 是否提供 通过垃圾收集和什么 你可以找到?
    • GC 何时执行 收集对象实例?

    应该比某些商品分析器更好地了解您,我注意到它们有时会根据您的分配 porofile 产生误导(顺便说一句。注意大型对象堆问题,> ~83kb 对象是经过特殊处理的,因为在这种情况下,我会建议您从大型对象堆中退出 :)。

    鉴于您的 cmets,还有一些事情......

    我之前发布过关于图像加载不收费配额或任何其他可分辨统计信息的帖子,这意味着,您可能需要追踪一些句柄或加载程序问题(最终参见加载程序锁定),但在此之前,您可以尝试设置增加一些Constrained Execution Regions,它们可以创造奇迹,但不幸的是,它们也很难改装成非纯代码。

    这篇最近的MSDN Mag,文章文档中有很多perfmon类型的内存sperlunking(后续this older one)。

    来自VS Perf Blog,他们展示了如何在visual studio中使用SOS,可以很方便地追踪rouge DLL,相关帖子也不错。

    Maoni Stephen's Blogcompany,他说他是 perf 团队的一员,但基本上他 100% 的帖子都与 GC 有关,所以他很可能会写。

    Rick Byers 是 CLR 诊断团队的开发人员,他的许多博客好友也是不错的来源,但是,我强烈建议您也参考全新的 dev/diagnostics forum。他们最近扩大了讨论范围。

    Code Coverage Toolstracing 通常可以提供帮助,让您大致了解实际运行的情况。

    (具体来说,那些特定的统计数据可能无法让您全面了解困扰您的代码的原因,我可以说最近我发现(即使使用 .net4beta 二进制文件,来自this company 的分析器也相当不错,它能够从其配置文件跟踪中获取本机/托管泄漏,带您回到确切的源代码行(即使经过优化,也非常好(并且它有 30 天的试用期))))。

    祝你好运!!希望其中的一些帮助,这只是我的记忆,因为我现在正在做很多相同的工作;)

    【讨论】:

    • 谢谢 - 这里有一些不错的 cmets。事情是这样的:.NET 性能对象中的“# Total committed Bytes”和“Large Object Heap 大小”度量似乎都没有表明 .NET 对象存在泄漏。现在我比 CLR 更熟悉在 Native 代码中调试内存泄漏,所以也许我误解了这些数字的含义。你能纠正我吗?上述性能对象中会出现 CLR 内存泄漏吗?
    【解决方案3】:

    试用:DebugDiags.
    生成一些内存转储后,它会给你一个很好的总结,告诉你分配了哪些内存,并且根据找到你的 PDB,它可以告诉你它是由谁分配的。

    【讨论】:

      【解决方案4】:

      您可能有参考泄漏,请查看 ANTS 分析软件。 Ants Profiler

      引用泄漏是内存泄漏的 .net 等价物,您持有对阻止垃圾回收的对象的引用,因此您使用的内存开始增加。

      【讨论】:

      • 但这不会出现在“# Bytes in all Heaps”.NET 内存值中吗?我一直在 Process Explorer 中观看它并且它没有泄漏。泄漏是在私有字节中,而不是在“# Bytes in all Heaps”中。
      【解决方案5】:

      如果您使用 GDI+ 和许多其他 API,您是否可能错过了一些处理程序。

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

      【讨论】:

      • 好主意。我猜这些泄漏不会显示为 CLR 内存使用,因为它们是本机的,但也不会出现在本机泄漏检测工具中,因为它们不是我的代码。我会重击(虽然​​我不知道 FXCop 是否可以处理混合模式 C++/CLI)
      • 希望对您有所帮助。我只是在黑暗中拍摄,但我在一个执行 GDI 的应用程序中看到了这种情况,我发现 GDI+ 中的几乎每个对象都需要一个处理器。请注意,这是在“使用”之前的一天
      猜你喜欢
      • 2017-07-22
      • 1970-01-01
      • 2012-06-24
      • 2012-09-16
      • 1970-01-01
      • 1970-01-01
      • 2016-01-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多