了解内存性能计数器

时间:2010-09-21 08:17:11

标签: c# performance performancecounter

[更新 - 2010年9月30日]

因为我在这个& amp;相关主题,我会根据我在这里的答案中提供的经验和建议,写出我收集的任何提示 -

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.net/2005/06/30/perfmon-your-debugging-buddy/

我的问题:

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

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

  1. 向我展示谁分配了大量内存

  2. 哪种数据类型使用了最大内存

  3. 但是,CLR Profiler和Performance Counters(因为它们共享相同的数据)都无法解释:

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

    2. 如何比较每次运行后的性能数据 - 特定计数器的好坏程度是低还是高?


    3. 我需要什么:

      我正在寻找以下提示:

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

      2. 如果发生碎片,如何压缩内存 - 因为似乎.NET GC并没有真正有效地执行此操作并导致OOM。

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

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

      5. 我已经知道的提示

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

        2. 避免创建对象/数组> 85KB - 这不在我的掌控之中。所以,可能会有很多LOH。

        3. 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问题,我只专注于与内存相关的计数器。

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

3 个答案:

答案 0 :(得分:2)

您可以尝试自己汇集和管理大型对象。例如,如果你经常需要< 500k数组并且一次活动的数组很容易被理解,那么你可以避免将它们解除分配 - 这样一来,如果你一次只需要10个数组,你可能会受到影响固定的5mb内存开销,而不是麻烦的长期碎片。

至于你的三个问题:

  1. 是不可能的。只有垃圾收集器决定何时完成托管对象并释放内存。这是他们管理对象的部分原因。

  2. 如果您在不安全的代码中管理自己的堆并完全绕过大对象堆,则可以这样做。如果你走这条路,你最终会做很多工作并且会带来很多不便。我怀疑这对你来说是值得的。

  3. 这是对象的大小,而不是数组中元素的数量。

  4. 请记住,碎片仅在释放对象时发生,而不是在分配对象时发生。如果碎片确实是你的问题,重复使用大对象将有所帮助。专注于在应用程序的生命周期中创建更少的垃圾(尤其是大型垃圾),而不是直接尝试处理gc实现的细节。

答案 1 :(得分:2)

Nayan,以下是您的问题的答案,以及其他一些建议。

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

    • 已经提出的建议:预分配和汇集缓冲区。

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

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

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

    此外,如果您共享应用程序的内存相关性能计数器的某些值将非常有用:进程的专用字节和工作集,BIAH,总提交字节数,LOH大小,Gen0,Gen1,Gen2大小,# Gen0,Gen1,Gen2集合,GC中的%时间。这有助于更好地理解您的问题。

答案 2 :(得分:0)

另一个指标是关注Private BytesBytes in all Heaps。如果Private Bytes的增长速度超过Bytes in all Heaps,则会出现非托管内存泄漏。如果“所有堆中的字节数”增加的速度超过“私有字节数”,那么这是一个可管理的泄漏。

纠正@Alexey Nedilko所说的话:

  

“LOH外部碎片比Gen2外部危险性小   分裂,因为LOH没有被压缩。 LOH的空闲插槽可以   反而被重用。“

绝对不正确。 Gen2被压缩,这意味着收集后永远不会有自由空间。 LOH没有被压缩(正如他正确提到的那样),是的,免费插槽被重复使用。 但如果可用空间不是连续的以符合所请求的分配,那么分段大小会增加 - 并且可以继续增长和增长。因此,你可以最终得到LOH中从未填补的空白。这是OOM的常见原因,我在很多内存转储中看到了这一点。我已经分析过了。

虽然现在GC API中的方法(从.NET 4.51开始)可以调用以编程方式压缩LOH,但我强烈建议避免这种情况 - 如果应用程序性能是一个问题。在运行时执行此操作非常昂贵,并且会显着损害您的应用程序性能。 GC的默认实现是高效的原因,这就是为什么他们首先省略了这一步骤。 IMO,如果你发现由于LOH碎片你必须调用它,你在你的应用程序中做错了 - 它可以通过池化技术,拆分数组和其他内存分配技巧来改进。如果这个应用程序是一个离线应用程序或某个批处理过程,其中性能不是很大,也许它不是那么糟糕,但我会充分利用它。

这是如何发生这种情况的一个很好的视觉示例 - The Dangers of the Large Object HeapLarge Object Heap Uncovered - by Maoni (GC Team Lead on the CLR)