17

我有一个问题,几个 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];

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

有没有正确的方法来做到这一点?

4

10 回答 10

15

这不是对原始问题“如何强制 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,那么首先尝试分配一大块内存并使用它们(可能需要编写一些帮助类来操作一个较小的数组,实际上它位于一个更大的数组中)以避免碎片。这可能会有所帮助,但是,它可能无法完全解决问题,您可能必须处理复杂性。
于 2009-07-09T17:17:19.397 回答
9

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

大对象堆

发现 CLR Inside Out 大对象堆

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

检查此问题以获取有关如何使用 SOS 检查 loh 的示例。

于 2009-07-09T18:24:54.393 回答
7

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

array1 = null;
GC.Collect();
array1 = new Vector3[210,210,210];
于 2009-07-09T14:50:09.130 回答
4

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

艾伦。

于 2009-07-09T17:21:08.437 回答
3

如果我不得不推测你的问题并不是你真的要从 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];
于 2009-07-09T14:59:38.097 回答
1

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

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

于 2009-07-09T14:59:16.837 回答
1

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

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

public static void Collect(ref object o)
{
    o = null;
    GC.Collect();
}
于 2009-07-09T17:42:47.737 回答
0

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

于 2009-07-09T17:03:20.500 回答
0

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

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

于 2009-07-09T17:23:05.530 回答
0

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

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

What I can assure you is that the GC will do the right thing trying to satisfy you allocation request, this includes kicking off a GC to clean the old garbage to satisfy the new allocation requests.

I don't know what is the policy of sharing out memory dumps on Stackoverflow, but I would be happy to take a look to understand your problem more.

于 2009-07-10T03:07:58.617 回答