C#垃圾回收器行为

3
我们有一个用C#编写的应用程序,控制着我们的一台设备,并响应该设备发出的信号。
基本上,该应用程序创建线程,处理操作(访问数据库等),并与该设备通信。
在应用程序的生命周期中,它创建对象并释放它们,到目前为止,我们让垃圾收集器负责我们的内存。我已经阅读过高度推荐不要干涉GC的工作。
现在我们面临的问题是,我们的应用程序进程会无限增长,逐步增长。例如:

enter image description here

当应用程序增长时,似乎会出现“波浪”,突然释放一些内存,但同时似乎会留下内存泄漏。我们正在尝试使用一些内存分析工具来调查应用程序,但我们希望深入了解垃圾回收器的工作原理。
我在这里找到了一篇很好的文章:大对象的危险,我也在官方文档中找到了资料:MSDN
你们知道其他非常深入的GC文档吗?
编辑:
这是一个截图,说明了应用程序的行为:
您可以清楚地看到我们在一个非常规律的模式下拥有“波浪”效应。
附属问题:
我发现我的GC Collection 2堆栈相当大,并且遵循与应用程序使用的总字节数相同的模式。我想这是完全正常的,因为我们的大多数对象都将至少经历2次垃圾回收(例如单例类等)...你认为呢?

让我说一下。我花了几周时间追踪与.NET内存消耗相关的问题,我学到了这一点。如果您正确构建对象并正确删除引用,则内存会被___立即___收集。如果您做得正确,那么需要循环才能收集内存是一个谎言。__因此__,请发布一些好的代码,以便我们了解您如何构建对象__以及__对象看起来像其他对象的引用和反之亦然。 - Mike Perrenoud
1
你是如何测量内存使用情况的? - spender
我假设'Mo'是指兆字节?无论如何,您提供的数据并没有表明存在内存泄漏,甚至没有问题的迹象。 - H H
@Mike 我知道问题出在我们这边,我并不怪垃圾回收器... 实际上,我想了解它的工作方式以改进我的代码... 特别是,关于大对象的文章让我感到非常惊讶,现在我知道我必须注意这个问题! - Andy M
PerfView(http://blogs.msdn.com/b/vancem/archive/2011/12/28/publication-of-the-perfview-performance-analysis-tool.aspx和http://www.microsoft.com/en-za/download/details.aspx?id=28567)是一个探索.NET内存管理问题的绝佳工具。您将准确地了解到谁在使用您的内存。 - Govert
显示剩余2条评论
3个回答

1
描述的行为通常是由于在大对象堆(LOH)上创建的对象问题所致。但是,您的内存消耗似乎稍后会返回一些较低的值,因此请仔细检查它是否真的是LOH问题。
显然,您已经知道了这一点,但不太明显的是,有一个例外,即 LOH 上的对象大小。
如文档中所述,大小超过 85000 字节的对象最终会出现在 LOH 上。然而,由于某种原因(可能是“优化”),长度超过 1000 个元素的双精度数组也会出现在那里:
double[999] smallArray = ...; // ends up in 'normal', Gen-0 heap
double[1001] bigArray = ...; // ends up in LOH

这些数组可能导致碎片化的 LOH,需要更多的内存,直到出现内存不足异常。

我曾经遇到过这个问题,因为我们的应用程序接收一些传感器读数作为双精度数组,由于每个数组的长度略有不同(这些是由非实时进程采样的各种频率的实时数据读数),导致了 LOH 碎片化。我们通过实现自己的缓冲池来解决了这个问题。


我最近才意识到大对象问题...实际上我们可能确实会遇到这个问题!我正在开始调试,希望在快速得出结论之前收集所有信息! - Andy M
我想知道为什么微软这样做?我知道访问8字节对齐的双精度浮点数比非对齐的双精度浮点数更快,但是当它们在100个元素的数组中或者1000个元素的数组中时,情况也是一样的。此外,由于缓存行的存在,双精度浮点数在这方面并不是唯一的;适合缓存行的对象比跨越它们的对象更有效率。考虑到许多程序分配了大量的小对象,我认为为“备用”12、20、28或36字节块的指针提供效率会更高,并且在分配这些大小的对象时... - supercat
检查是否有适当大小的“备用”块可用。如果有,使用它;如果没有,则分配两倍于正常空间并设置一个“备用”块。对于任何大于36字节的奇数大小分配,向上舍入到最近的8个字节。由于需要舍入的最小对象为44字节,内存浪费将低于10%。每个大于36字节的对象 - 不仅是1000个或更多元素的双倍数组 - 都将具有8字节对齐。 - supercat

1

我对我几年前教授的一个课程进行了一些研究。我认为这些参考资料中没有包含关于LoH的任何信息,但我认为分享它们仍然是值得的(请参见下文)。此外,我建议在指责垃圾收集器之前,先执行第二次搜索未释放对象引用。只需在类终结器中实现计数器,以检查这些大型对象是否被丢弃,就像您所认为的那样。

解决此问题的另一种方法是,简单地不释放您的大型对象,而是使用池化策略重复使用它们。出于我的傲慢,我曾多次过早地指责GC,认为应用程序的内存需求随着时间的推移而增长,然而这往往是错误实现的症状。

GC参考文献: http://blogs.msdn.com/b/clyon/archive/2007/03/12/new-in-orcas-part-3-gc-latency-modes.aspx http://msdn.microsoft.com/en-us/library/ee851764.aspx http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx http://blogs.msdn.com/b/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx

Eric Lippert的博客在深入理解任何C#方面时尤其有趣!


非常感谢您提供的所有信息,我会仔细阅读它们! - Andy M

0

这里是我的一些调查更新:

在我们的应用程序中,我们使用了很多线程来执行不同的任务。其中一些线程具有更高的优先级。

1)我们正在使用一个并发的GC,并尝试将其切换回非并发

我们看到了显著的改进:

  • 垃圾收集器被调用得更频繁,当更频繁地调用时,它释放内存的效果更好。

我会尽快发布一张截图来说明这一点。

我们找到了一篇非常好的文章{{link1:在MSDN上}}。我们还在{{link2:SO}}上发现了一个有趣的问题。

在下一个4.5框架中,将提供4种GC配置选项。

  1. 工作站 - 非并发
  2. 工作站 - 并发
  3. 服务器 - 非并发
  4. 服务器 - 并发

我们将尝试切换到“服务器 - 非并发”和“服务器 - 并发”以检查是否能提供更好的性能。

我会及时更新这个帖子,分享我们的发现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接