强制数组的垃圾收集,C#

时间:2009-07-09 14:46:18

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

我遇到一个问题,其中一对三维数组分配了大量内存,程序有时需要用更大/更小的内存替换它们并抛出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];

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

有没有正确的方法呢?

10 个答案:

答案 0 :(得分:15)

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

看到你的评论后,

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

我怀疑你可能有内存碎片。如果对象很大(如果我没记错的话,在.net 2.0 CLR下85000字节,我不知道它是否已被更改),该对象将被分配在一个特殊的堆中,大对象堆(LOH) 即可。 GC确实回收了LOH中无法访问的对象所使用的内存,但由于性能的原因,在LOH中不执行压缩,,因为它对其他堆(gen0,gen1和gen2)执行压缩。

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

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

  1. 转移到64位计算机/操作系统并利用它:)(最简单,但可能最难,取决于您的资源限制)
  2. 如果你不能做#1,那么先尝试分配一大堆内存然后使用它们(可能需要编写一些辅助类来操作一个较小的数组,实际上它存在于一个更大的数组中)以避免碎片化。这可能有点帮助,但它可能无法完全解决问题,您可能不得不处理复杂性。

答案 1 :(得分:9)

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

Large Object Heap

CLR Inside Out Large Object Heap Uncovered

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

检查此question以获取如何使用SOS检查loh的示例。

答案 2 :(得分:7)

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

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

答案 3 :(得分:4)

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

艾伦。

答案 4 :(得分:2)

如果我不得不推测你的问题并不是你从Vector3 [200,200,200]到Vector3 [210,210,210],但很可能你之前有类似的前一步:

 i.e.   
    // first you have
    Vector3[10,10,10];
    // then
    Vector3[20,20,20];
    // then maybe
    Vector3[30,30,30];
    //  .. and so on ..
    //  ...
    // then
    Vector3[200,200,200];
    // and eventually you try
    Vector3[210,210,210] // and you get an 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];

答案 5 :(得分:1)

他们可能没有被收集,因为他们被引用到你没想到的地方。

作为测试,请尝试更改对WeakReferences的引用,看看是否可以解决您的OOM问题。如果没有,那么你就会在其他地方引用它们。

答案 6 :(得分:1)

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

那就是说,如果你想要那个功能,为什么不创建呢?

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

尝试将其分配为锯齿状数组 - Vector3 [210] [210] [210] - 将数组分布在内存而不是单个块中,并查看是否有所改善

答案 9 :(得分:0)

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

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

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

我不知道在Stackoverflow上共享内存转储的策略是什么,但我很乐意更多地了解您的问题。