了解内存性能计数器

Nay*_*yan 5 c# performance performancecounter

[更新 - 2010年9月30日]

由于我对这个及相关主题进行了很多研究,我会写出我从我的经验和建议中收集的任何提示,这些提示在这里给出了答案 -

1)使用内存分析器(尝试CLR Profiler,开始)并找到消耗max mem并对其进行微调的例程,如重用大数组,尝试将对象的引用保持在最小.

2)如果可能,分配小对象(对于.NET 2.0小于85k)并使用内存池,如果可以避免垃圾收集器的高CPU使用率.

3)如果增加对象的引用,则负责将它们取消引用相同的次数.你会安心,代码可能会更好.

4)如果没有任何作用且您仍然无能为力,请使用消除方法(注释/跳过代码)来找出消耗最多内存的内容.

在代码中使用内存性能计数器也可能对您有所帮助.

希望这些帮助!


[原始问题]

嗨!

我在C#工作,我的问题是内存不足异常.

我在这里读了一篇关于LOH的优秀文章 - > http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/

太棒了!

并且, http://dotnetdebug.ne​​t/2005/06/30/perfmon-your-debugging-buddy/

我的问题:

我在企业级桌面应用程序中遇到内存不足问题.我试着阅读并理解有关内存分析和性能计数器的内容(尝试过WinDBG! - 一点点)但我仍然对基本内容毫无头绪.

我尝试使用CLR分析器来分析内存使用情况.它有助于:

  1. 告诉我谁分配了大量的内存

  2. 什么数据类型使用最大内存

但是,CLR Profiler和性能计数器(因为它们共享相同的数据)都无法解释:

  1. 每次运行应用程序后收集的数字 - 如何理解是否有任何改进?!?!

  2. 如何比较每次运行后的性能数据 - 特定计数器的优先级是低还是高?


我需要的:

我正在寻找以下提示:

  1. 如何释放(是,正确)托管数据类型对象(如数组,大字符串) - 但如果可能的话,不要通过进行GC.Collect调用.我必须时不时地处理长度为500KB(不可避免的大小:-()的字节数组.

  2. 如果发生碎片,如何压缩内存 - 因为看起来.NET GC并没有真正有效地做到这一点并导致OOM.

  3. 另外,LOH究竟有85KB的限制?这是数组整体大小的对象大小吗?这对我来说不是很清楚.

  4. 哪些内存计数器可以判断代码更改是否实际上减少了OOM的可能性?

提示我已经知道了

  1. 将托管对象设置为null - 将它们标记为垃圾 - 以便垃圾收集器可以收集它们.这很奇怪 - 在将string []对象设置为null之后,所有Heaps中#个字节都会出现!

  2. 避免创建> 85KB的对象/数组 - 这不在我的控制范围内.所以,可能会有很多LOH.

3.

Memory Leaks Indicators:

# bytes in all Heaps increasing
Gen 2 Heap Size increasing
# GC handles increasing
# of Pinned Objects increasing
# total committed Bytes increasing
# total reserved Bytes increasing
Large Object Heap increasing

我的情况:

  • 我有4 GB,32位机器,上面有Wink 2K3服务器SP2.
  • 我知道应用程序可以使用<= 2 GB的物理RAM
  • 增加虚拟内存(页面文件)大小在此方案中无效.

作为其OOM问题,我只关注与内存相关的计数器.

请指教!由于缺乏良好的文档,我真的需要一些帮助.

Ale*_*lko 2

Nayan,这是您问题的答案,以及一些额外的建议。

  1. 你无法释放它们,只能让它们更容易被GC收集。看来你已经知道方法了:关键是减少对象的引用次数。
  2. 碎片化是另一件你无法控制的事情。但有几个因素可能会影响这一点:
    • LOH 外部碎片比 Gen2 外部碎片危险性小,因为 LOH 未压缩。LOH 的空闲时隙可以重新使用。
    • 如果所引用的 500Kb 字节数组用作某些 IO 缓冲区(例如传递给某些基于套接字的 API 或非托管代码),则它们很可能会被固定。固定对象无法通过 GC 进行压缩,它们是堆碎片最常见的原因之一。
    • 85K 是对象大小的限制。但请记住,System.Array 实例也是一个对象,因此所有 500K byte[] 都在 LOH 中。
    • 您帖子中的所有计数器都可以提示有关内存消耗变化的信息,但就您而言,我会选择 BIAH(所有堆中的字节数)和 LOH 大小作为主要指标。BIAH 显示所有托管堆的总大小(Gen1 + Gen2 + LOH,准确地说,没有 Gen0 - 但谁关心 Gen0,对吧?:)),LOH 是放置所有大 byte[] 的堆。

建议:

  • 已经有人提出了一些建议:预分配和池化缓冲区。

  • 如果您可以使用任何集合而不是连续的字节数组(如果在 IO 中使用缓冲区则不是这种情况),则另一种方法可能会很有效:实现一个自定义集合,该集合在内部由许多较小尺寸的数组组成。这与 C++ STL 库中的 std::deque 类似。由于每个单独的数组都小于 85K,因此整个集合不会进入 LOH。使用此方法可以获得的优势如下:仅当发生完整 GC 时才会收集 LOH。如果应用程序中的 byte[] 不是长期存在的,并且(如果它们的大小较小)会在收集之前进入 Gen0 或 Gen1,这将使 GC 的内存管理变得更加容易,因为 Gen2 收集更加重量级。

  • 关于测试和监控方法的建议:根据我的经验,GC 行为、内存占用和其他与内存相关的东西需要监控相当长的时间才能获得一些有效且稳定的数据。因此,每次更改代码中的某些内容时,都要进行足够长的测试,监视内存性能计数器,以了解更改的影响。

  • 我还建议查看 % Time in GC 计数器,因为它可以很好地指示内存管理的有效性。该值越大,您的应用程序花费在 GC 例程上的时间就越多,而不是处理用户的请求或执行其他“有用”操作。我无法就该计数器的绝对值指示问题给出建议,但我可以分享我的经验供您参考:对于我正在开发的应用程序,我们通常将 GC 时间百分比高于 20% 视为问题。

此外,如果您共享应用程序的一些与内存相关的性能计数器的值,也会很有用:进程的专用字节和工作集、BIAH、总提交字节、LOH 大小、Gen0、Gen1、Gen2 大小、Gen0 的数量、 Gen1、Gen2 收集,GC 时间百分比。这将有助于更好地理解您的问题。