【问题标题】:.Net Memory Management Issue : Objects Stuck in Generation 2.Net 内存管理问题:对象卡在第 2 代
【发布时间】:2011-06-30 11:36:40
【问题描述】:

我已经使用 VS2010 分析器分析了我的应用程序,并启用了对象生命周期收集。
看到名为“Record”的特定结构的大多数实例都被 GC 收集为第 2 代实例,我感到非常惊讶。我很沮丧,因为“记录”结构的实例每个(理论上)应该活不到 500 毫秒。

这些结构是 6xInt32 左右的简单时间序列数据,在流中读取,在大小为 1000 的队列中排队/出队,传递给处理器,该处理器根据数百万“记录”触发一些逻辑依次。我不需要一次保存超过 50 条记录。

所以我的问题是:为什么这些对象的寿命足够长,主要最终成为第二代引用,我可以做些什么来确保它们在每次计算后真的被丢弃。

编辑: 我之所以这样问,是因为我注意到更大样本量(即记录编号)的性能急剧下降:如果 N 需要 T 分钟,2N 需要 2,5T 分钟左右,依此类推。
所以显然存在泄漏某处。

编辑 2: 我的坏:创建结构实例不会导致垃圾收集 我已将其更改为类,到目前为止没有发现任何重大改进。 这次我将使用类而不是结构再次运行分析器)并查看它提供了什么

编辑 3: 许多答案怀疑拳击/拆箱发生在某个地方。 我确实使用类型化的泛型集合和类型化的队列。并且“记录”永远不会作为成员附加到任何类。它们由事件单独处理。 ex-Struct(Now Class)实现了一个接口,并在调用时由它强制转换(这是相当常见的用法),我放弃了该接口。没有改善。

编辑 4: 我再次运行探查器,按类替换 struct。我有相同的结果:
CLASS“记录”的大多数实例最终仍被收集为 Gen2 实例

编辑 5: Record 类的生产者是许多并行的BackGroundWorkers(字节读取器),并且有一个消费者线程在执行一些检查后将 Records 分派给其他方法。此外,我使用 Events 和 Delegates 在不同部分之间进行通信。我不会取消注册这些事件,因为它们在整个过程中都很有用(在那一点上我可能错了)

【问题讨论】:

  • 您是否观察到可以连接到 gen2 中的记录的任何性能下降?如果不是,您为什么要担心 GC 的运作方式?
  • 它们是结构吗?不是类?
  • @Mika A)问题中写的一半时间与现实略有相似,B)我认为结构,除非作为类的字段,通常不会去到 .NET Fram 的“标准”实现中的堆(因此没有收集 GC)。和 C)在 6x4 字节的结构中,您超出了结构的“标准”“好”限制。
  • 关于“为什么”,您超出了良好的限制:每次将结构插入 Queue 时(您是在使用通用集合,还是在老式集合中装箱?),结构被复制(所以24个字节被读取和写入)。每次结构出列时,都会在局部变量(24 字节)中读取和写入。每次函数接收结构作为参数时,都会读取和复制 24 个字节。这就是为什么结构应该很小! (假设不超过 4 sizeof(ptr),所以 32 位系统上的 GUID 是一个好的结构的最大大小)。解释器可以优化。可以,不应该!
  • @mika:发生这种情况的原因有很多,解决方案也很多。您没有提供足够的信息来获得真正的帮助。我们只能猜测,而您只能得到猜测。

标签: c# .net performance memory-management


【解决方案1】:

如果您将它们存储为局部变量(根据您的情况可能会也可能不会),它们将永远不会最终出现在堆上。

如果可能的话,我建议您尝试一下。如果您发布代码示例,您可能会得到更深入的响应。

作为健全性检查,您是否忘记取消注册静态事件,或者使用其他可能正在执行此操作的类(某些类通过 Dispose 修复此问题)?

您是否也考虑过使用Flyweight pattern 的可能性?

编辑- 由于您现在说您正在处理事件,这很可能是您的问题的原因。您是否忘记取消注册活动?

【讨论】:

  • 呃?你确定吗 ?为什么局部变量会出现在 Gen 2 中?相反,我认为它们是本地的(声明为方法)这一事实将使它们“免于”被引用“ad vitam aeternam”......
  • 如果它们是你所说的结构并且它们是局部变量,它们将存在于堆栈中。
  • 不要装箱,也不要将它们存储为成员变量。现在听起来整个结构都不是问题,除非存在与传递结构相关的性能问题。
  • 装箱/拆箱将在哪里进行?我的队列已输入...?
  • 这是个好问题。我没有说它正在发生,但它可能是……一个可能的例子——如果你的结构实现了一个接口并且你把这个结构当作那个接口,那么这可能是盒子操作的一个原因。如果您担心拳击操作,可以使用 ildasm 来查看它们。
【解决方案2】:

它们最终成为第二代,因为 GC 只是决定不对它们进行垃圾收集。我不认为这真的是一个值得担心的问题。如果你真的想要,你可以重复使用这 1000 个对象,而不是不断地创建新对象。这将使这些对象不需要被垃圾收集。您可以强制垃圾收集以确保“真正”转储对象,但这会降低性能,因为 GC 是一项昂贵的操作,或者我被告知。

【讨论】:

  • 这比 GC 说的“我想我今天不会收集这些对象”要复杂一些。它们没有被收集,因为仍然有对它们的引用,或者它们被用于长期存在的对象。您希望在第二代中尽可能多地使用一些对象。
  • 是的,我想避免显式调用 GC.Collect。但这实际上是一个值得担心的问题,因为我有数百万个,而且我的计算最终运行得越来越慢。我会尽量不调用“new”关键字,而只是对现有对象进行成员更新(但这可能很难实现)
  • @George :是的,你完全正确。收集不是随机的。这就是为什么我想找到一种方法来终止对这些对象的未决引用。
【解决方案3】:

如果您对内存使用“很重”,并且使用的是 C# 4.0,则可以尝试“服务器”GC。将您的 app.config(或 web.config)与以下内容合并:

<configuration>
  <runtime>
    <gcServer enabled="true"/>
  </runtime>
</configuration>

(合并我的意思是,如果您已经拥有其中一些部分,请使用它们,否则创建它们。“配置”是 app.config 的第一级元素)。这对于某些应用程序(不需要与用户进行大量交互的应用程序)更好

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多