【问题标题】:Force garbage collection of arrays, C#强制对数组进行垃圾回收,C#
【发布时间】:2010-11-09 09:56:43
【问题描述】:

我有一个问题,几个 3 维数组分配了大量内存,程序有时需要用更大/更小的数组替换它们并抛出 OutOfMemoryException。

示例:分配了 5 个 96MB 数组(200x200x200,每个条目中有 12 个字节的数据),程序需要将它们替换为 210x210x210 (111MB)。它以类似于以下方式执行此操作:

array1 = new Vector3[210,210,210];

其中 array1-array5 与之前使用的字段相同。这应该将旧数组设置为垃圾收集的候选对象,但似乎 GC 动作不够快,并在分配新数组之前留下分配的旧数组 - 这会导致 OOM - 而如果它们在新分配之前被释放,则空间应该是够了。

我正在寻找一种方法来做这样的事情:

GC.Collect(array1) // this would set the reference to null and free the memory
array1 = new Vector3[210,210,210];

我不确定完整的垃圾收集是否是一个好主意,因为该代码可能(在某些情况下)需要相当频繁地执行。

有合适的方法吗?

【问题讨论】:

  • 这会导致OOM异常似乎很奇怪。你有一些示例代码来演示这个问题吗?
  • 这是一个相当大(仍未发布)的程序的一部分,不幸的是我只能发布实际代码的小sn-ps,没有足够大的可编译。
  • 你的问题不是内存管理,而是你使用了太大的数组。为什么你认为你首先需要那些大型阵列?你用它们做什么?
  • 程序进行体积渲染,这些数组保留传递给着色器的数据(要渲染的所有体素的位置、颜色和法线) - 我可以通过使用可调整大小的集合来减小大小(这只会保留实际渲染的那些)但这会杀死FPS。我能想到的唯一选择是使用光线投射或类似的东西从头开始重写渲染代码,但在不久的将来我没有时间这样做。

标签: c# arrays garbage-collection xna out-of-memory


【解决方案1】:

这不是对原始问题“如何强制 GC”的确切答案,但我认为它会帮助您重新检查您的问题。

看到你的评论后,

  • 放置 GC.Collect();似乎确实有帮助,尽管它仍然不能完全解决问题 - 由于某种原因,当分配大约 1.3GB 时程序仍然崩溃(我正在使用 System.GC.GetTotalMemory( false ); 来查找实际分配的数量)。

我怀疑你可能有内存碎片。如果对象很大(我没记错的话.net 2.0 CLR下85000字节,不知道有没有改过),对象会被分配到一个特殊的堆中,Large Object Heap (LOH) 。 GC 确实回收了 LOH 中无法访问的对象正在使用的内存,但是,它不会像其他堆(gen0、gen1 和 gen2)那样在 LOH 中执行压缩,因为性能原因。

如果你经常分配和释放大对象,它会使 LOH 碎片化,即使你有比你需要的更多的可用内存,你可能不再有连续的内存空间,因此会出现 OutOfMemory 异常。

我现在可以想到两种解决方法。

  1. 迁移到 64 位机器/操作系统并充分利用它 :)(最简单,但也可能最难,具体取决于您的资源限制)
  2. 如果您不能执行 #1,则尝试先分配大量内存并使用它们(可能需要编写一些辅助类来操作较小的数组,实际上该数组位于较大的数组中)以避免碎片.这可能会有所帮助,但是,它可能无法完全解决问题,您可能必须处理复杂性。

【讨论】:

  • 或者切换到使用锯齿状数组 - Vector3[210][210][210],这将拆分分配而不使用一大块内存
  • 看来问题确实是由这个碎片问题引起的。不幸的是,我不能将程序移植到 64 位,因为 XNA 只支持 32 位。在启动时分配一个大数组可能不值得在小机箱(低于 100x100x100)上额外的内存以及使其正常工作的额外工作。我做了数学计算,即使我可以使用完整的 2GB,最大卷大小的改进也不够大(从大约 200 ^ 3 到 300 ^ 3)来解决寻找/实施黑客使其工作的麻烦,至少现在不是。感谢所有试图提供帮助的人!
【解决方案2】:

您似乎遇到了 LOH(大对象堆)碎片问题。

Large Object Heap

CLR Inside Out Large Object Heap Uncovered

您可以使用 SOS 检查您是否有 loh 碎片问题

查看question 以获取有关如何使用 SOS 来检查 loh 的示例。

【讨论】:

    【解决方案3】:

    强制垃圾回收并不总是一个好主意(在某些情况下它实际上可以提高对象的生命周期)。如果必须,您将使用:

    array1 = null;
    GC.Collect();
    array1 = new Vector3[210,210,210];
    

    【讨论】:

    • 不,您根本不应该调用 GC.Collect。最好的解决方案是在分配新数组之前删除引用,让垃圾收集器在不干扰的情况下完成它的工作。
    • 如果运行时找不到足够的内存进行分配,它将触发收集,因此无需显式调用 GC.Collect。
    • John Doe 确实询问了 forcing 垃圾回收的问题。
    • @icelava:那只是因为他认为他需要强制垃圾收集,但如果你只是删除引用它就可以正常工作而不强制它。
    • 把 GC.Collect();似乎确实有帮助,尽管它仍然没有完全解决问题 - 由于某种原因,当分配了大约 1.3GB 时程序仍然崩溃(我正在使用 System.GC.GetTotalMemory( false ); 来查找实际分配的数量)。只是取消引用根本没有帮助。
    【解决方案4】:

    这不只是大对象堆碎片吗? > 85,000 字节的对象分配在大对象堆上。 GC 会释放此堆中的空间,但不会压缩剩余的对象。这可能会导致连续内存不足,无法成功分配大对象。

    艾伦。

    【讨论】:

      【解决方案5】:

      如果我不得不推测你的问题并不是你真的要从 Vector3[200,200,200] 到 Vector3[210,210,210] 而是你很可能在此之前有类似的先前步骤:

      IE。 // 首先你有 向量3[10,10,10]; // 然后 向量3[20,20,20]; // 那么也许 向量3[30,30,30]; // .. 等等 .. // ... // 然后 向量3[200,200,200]; // 最后你尝试 Vector3[210,210,210] // 你会得到一个 OutOfMemoryException..

      如果这是真的,我会建议一个更好的分配策略。尝试过度分配 - 可能每次都将大小翻倍,而不是总是只分配您需要的空间。特别是如果这些数组曾经被需要固定缓冲区的对象使用(即,如果它与本机代码有联系)

      所以,代替上面的,有这样的东西:

       // first start with an arbitrary size
       Vector3[64,64,64];
       // then double that
       Vector3[128,128,128];
       // and then.. so in thee steps you go to where otherwise 
       // it would have taken you 20..
       Vector3[256,256,256];
      

      【讨论】:

      • 请注意,一些内置的集合类会为您执行此操作。
      • 我同意,像这样不断地重新分配数组只是在乞求 OOM 错误。
      • 实际上,数组的“调整大小”是由用户输入触发的,并且是不可预测的(尽管我在测试步骤中手动增加了它)。过度分配不是一种选择,实际上会使它更早崩溃,因为总大小以立方方式增加。
      【解决方案6】:

      它们可能不会被收集,因为它们被引用到了您意想不到的地方。

      作为测试,请尝试将您的引用更改为 WeakReferences,看看是否能解决您的 OOM 问题。如果不是,那么您将在其他地方引用它们。

      【讨论】:

      • 它们仅用于生成它们的代码和使用数据的代码(XNA VertexBuffer,用于渲染体积),所以我认为没有其他任何引用它们。我以前从未使用过 WeakReferences,但从那篇文章中我了解到,它们有可能在其余的执行过程中被释放 - 这会给其余的代码带来严重的问题。
      【解决方案7】:

      我了解您正在尝试做的事情,并且推动立即进行垃圾收集可能不是正确的方法(因为 GC 的方式很微妙并且容易激怒)。

      也就是说,如果您想要该功能,为什么不创建它?

      public static void Collect(ref object o)
      {
          o = null;
          GC.Collect();
      }
      

      【讨论】:

      • 这没什么。您正在复制对对象 o 的引用指向 null,这对原始对象 o 没有任何作用。
      • 你明白,如果我对 ref 变量产生副作用,它也会改变调用者的副本,对吧?
      • 给我一个+1,这段代码确实会将原始变量设置为null,因为它正在通过引用修改原始变量的指针。
      【解决方案8】:

      OutOfMemory 异常会在内部自动触发一次 GC 循环,并在实际将异常抛出到您的代码之前再次尝试分配。您可能会遇到 OutOfMemory 异常的唯一方法是您持有对过多内存的引用。尽快清除引用,将它们分配为 null。

      【讨论】:

        【解决方案9】:

        问题的一部分可能是您分配了一个多维数组,该数组表示为大型对象堆上的单个连续内存块(更多详细信息here)。这可能会阻塞其他分配,因为没有可用的连续块可供使用,即使某处仍有一些可用空间,因此 OOM。

        尝试将其分配为锯齿状数组 - Vector3[210][210][210] - 将数组分布在内存周围而不是单个块,看看这是否会改善问题

        【讨论】:

          【解决方案10】:

          John,创建对象 > 85000 字节将使对象最终出现在大对象堆中。大对象堆永远不会被压缩,而是再次重用空闲空间。 这意味着如果您每次都分配更大的数组,您最终可能会遇到 LOH 碎片化的情况,从而导致 OOM。

          您可以通过在 OOM 点中断调试器并获取转储来验证这种情况,通过连接错误 (http://connect.microsoft.com) 将此转储提交给 MS 将是一个很好的开始。

          我可以向您保证,GC 会做正确的事情来满足您的分配请求,这包括启动 GC 以清理旧垃圾以满足新的分配请求。

          我不知道在 Stackoverflow 上共享内存转储的政策是什么,但我很乐意查看以进一步了解您的问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-01
            • 2012-12-06
            • 2011-01-26
            • 2011-11-29
            • 2021-12-20
            • 2010-09-19
            相关资源
            最近更新 更多