了解内存性能计数器

[更新 – 2010年9月30日]

由于我在这个及相关主题上进行了很多研究,我会根据我在这里的答案中提供的经验和建议,写下我收集到的任何提示 –

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

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

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

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

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

希望这些帮助!


[原始问题]

嗨!

我在C#工作,我的问题是内存不足exception。

我在这里读了一篇关于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和Performance Counters(因为它们共享相同的数据)都无法解释:

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

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


我需要的:

我正在寻找以下提示:

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

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

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

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

提示我已经知道了

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

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

3。

 内存泄漏指标:

所有堆中的#个字节数增加
 Gen 2堆大小增加
 #GG处理增加
固定对象数量增加
 #total committed字节数增加
 #total reserved字节增加
大对象堆增加 

我的情况:

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

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

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

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

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

建议:

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

  • 如果您可以使用任何集合而不是连续的字节数组(如果在IO中使用缓冲区则不是这种情况),则可以采用不同的方法:实现自定义集合,内部将由许多较小的数组组成。 这类似于来自C ++ STL库的std :: deque。 由于每个单独的arrays将小于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中的%时间。 这有助于更好地了解您的问题。

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

至于你的三个问题:

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

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

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

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

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

要纠正@Alexey Nedilko所说的话:

“LOH外部碎片比Gen2外部碎片更不危险,因为LOH没有压实.LOH的空闲插槽可以重复使用。”

绝对是不正确的 。 Gen2被压缩,这意味着收集后永远不会有自由空间。 LOH没有被压缩(正如他正确提到的那样),是的,重用了免费插槽。 但是如果可用空间不是连续的以满足所请求的分配,则段大小会增加 – 并且可以继续增长和增长 。 因此,您最终可能会在LOH中留下永不填充的空白。 这是OOM的常见原因,我在很多内存转储中看到了这一点。

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

这是如何发生的一个很好的视觉例子 – 大型物体堆的危险和大型物体堆未被发现 – 由Maoni(CLR的GC团队负责人)