【问题标题】:C# Garbage Collector behaviorC# 垃圾收集器行为
【发布时间】:2012-10-01 11:47:09
【问题描述】:

我们有一个 C# 应用程序,它控制我们的一个设备并对该设备给我们的信号做出反应。

基本上,应用程序创建线程、处理操作(访问数据库等)并与此设备通信。

在应用程序的生命周期中,它创建对象并释放它们,到目前为止,我们让垃圾收集器处理我们的内存。我读到强烈建议让 GC 在不干扰的情况下完成其工作。

现在我们面临的问题是,我们的应用程序的过程会不断增长,并且会逐步增长。示例:

当应用程序增长时似乎有“波”,突然间,应用程序释放了一些内存,但同时似乎留下了内存泄漏。

我们正在尝试使用一些内存分析器来调查应用程序,但我们想深入了解垃圾收集器的工作原理。

你们知道另一个非常深入的 GC 文档吗?

编辑:

这是一个说明应用程序行为的屏幕截图:

您可以清楚地看到我们在非常规则的模式上产生的“波浪”效果。

附属问题:

我发现我的 GC 收集 2 堆非常大,并且遵循与我的应用程序使用的总字节数相同的模式。我想这很正常,因为我们的大多数对象都会在至少 2 次垃圾回收中存活下来(例如 Singleton 类等)......你怎么看?

【问题讨论】:

  • 让我这么说吧。我花了数周时间追踪与 .NET 中的内存消耗相关的问题,并且我学到了这一点。如果您正确构建对象并正确删除引用,则会立即收集内存。如果你做得正确,它需要循环来收集内存是一个神话。 所以,请为我们发布一些代码,以了解您如何构建对象对象看起来像其他对象的引用,反之亦然。
  • 你是如何测量内存使用的?
  • 我假设“Mo”是兆字节?无论如何,您提供的数据并不表示内存泄漏,甚至不是问题。
  • Mo 绝对代表 MegaOctets,这意味着法语中的兆字节 :) 对不起,我一直犯那个愚蠢的错误!
  • @spender 我只是用 windows 的任务管理器检查了这个,查看了我的进程内存大小......

标签: c# garbage-collection


【解决方案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 碎片整理,因为每个数组的长度略有不同(这些是各种频率的实时数据读数,由非实时采样过程)。我们通过实现自己的缓冲池解决了这个问题。

【讨论】:

  • 我最近才意识到这个大对象问题......事实上我们很可能遇到这个问题!我正在调试这个旅程的开始,我想在此之前收集所有信息以便快速得出结论!
  • 我想知道微软为什么这样做?我知道访问 8 字节对齐的双精度数比非对齐的双精度数更快,但是当它们在 100 的数组中作为 1,000 的数组时也是如此。此外,由于缓存行,双打在这方面几乎不是唯一的。适合缓存行的对象比跨越它们的对象更有效。鉴于许多程序分配了很多小对象,我认为拥有 12、20、28 或 36 字节的“备用”块的指针以及分配其中一种大小的对象时会很有效。 ..
  • ...检查大小合适的“备用”块是否可用。如果是这样,请使用它;如果没有,分配两倍的正常空间量并留出一个“备用”块。对于任何更大的奇数大小分配,向上舍入到最接近的 8 个字节。由于需要四舍五入的最小对象为 44 字节,因此内存浪费将低于 10%。每个大于 36 字节的对象——不仅仅是包含 1000 个或更多元素的双精度数组——都将是 8 字节对齐的。
【解决方案2】:

我对几年前教过的一门课做了一些研究。我不认为这些参考资料包含有关 LoH 的任何信息,但我认为无论以哪种方式分享它们都是值得的(见下文)。此外,我建议在指责垃圾收集器之前对未释放的对象引用进行第二次搜索。只需在类终结器中实现一个计数器来检查这些大对象是否如您所想的那样被丢弃。

这个问题的一个不同的解决方案是永远不要释放你的大对象,而是用pooling strategy重用它们。在我的狂妄自大之前,我曾多次将我的应用程序的内存需求随着时间的推移而过早地归咎于 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#!

【讨论】:

  • 非常感谢您提供所有这些信息,我会仔细阅读它们!
【解决方案3】:

以下是我的一些调查的更新:

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

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

我们看到了巨大的进步:

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

只要我有一个好的截图来说明这一点,我就会发布一个截图。

我们发现了一篇非常好的文章on the MSDN。我们还在SO 上发现了一个有趣的问题。

在下一个 Framework 4.5 中,有 4 种可能性可用于 GC 配置。

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

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

我会根据我们的发现更新此线程。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多