[更新 - 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分析器来分析内存使用情况。它有用:
向我展示谁分配了大量内存
哪种数据类型使用了最大内存
但是,CLR Profiler和Performance Counters(因为它们共享相同的数据)都无法解释:
每次运行应用程序后收集的数字 - 如何理解是否有任何改进?!?!
如何比较每次运行后的性能数据 - 特定计数器的好坏程度是低还是高?
我需要什么:
我正在寻找以下提示:
如何释放(是,正确)托管的数据类型对象(如数组,大字符串) - 但如果可能的话,不要通过进行GC.Collect调用。我必须时不时地处理长度为500KB(不可避免的大小:-()的字节数组。
如果发生碎片,如何压缩内存 - 因为似乎.NET GC并没有真正有效地执行此操作并导致OOM。
此外,LOH究竟有85KB的限制?这是数组整体大小的对象大小吗?这对我来说不是很清楚。
如果代码更改实际上减少了OOM的可能性,哪些内存计数器可以判断?
我已经知道的提示
将托管对象设置为null - 将它们标记为垃圾 - 以便垃圾收集器可以收集它们。这很奇怪 - 在将 string [] 对象设置为null后,所有堆中的#个字节都会出现!
避免创建对象/数组> 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
我的情况:
由于其OOM问题,我只专注于与内存相关的计数器。
请指教! 由于缺乏良好的文档,我真的需要一些帮助!
答案 0 :(得分:2)
您可以尝试自己汇集和管理大型对象。例如,如果你经常需要< 500k数组并且一次活动的数组很容易被理解,那么你可以避免将它们解除分配 - 这样一来,如果你一次只需要10个数组,你可能会受到影响固定的5mb内存开销,而不是麻烦的长期碎片。
至于你的三个问题:
是不可能的。只有垃圾收集器决定何时完成托管对象并释放内存。这是他们管理对象的部分原因。
如果您在不安全的代码中管理自己的堆并完全绕过大对象堆,则可以这样做。如果你走这条路,你最终会做很多工作并且会带来很多不便。我怀疑这对你来说是值得的。
这是对象的大小,而不是数组中元素的数量。
请记住,碎片仅在释放对象时发生,而不是在分配对象时发生。如果碎片确实是你的问题,重复使用大对象将有所帮助。专注于在应用程序的生命周期中创建更少的垃圾(尤其是大型垃圾),而不是直接尝试处理gc实现的细节。
答案 1 :(得分:2)
Nayan,以下是您的问题的答案,以及其他一些建议。
建议:
已经提出的建议:预分配和汇集缓冲区。
如果你可以使用任何集合而不是连续的字节数组(如果在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 Bytes
与Bytes 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 Heap和Large Object Heap Uncovered - by Maoni (GC Team Lead on the CLR)