考虑下面的代码:
using System;
namespace memoryEater
{
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("alloc 1");
var big1 = new BigObject();
Console.WriteLine("alloc 2");
var big2 = new BigObject();
Console.WriteLine("null 1");
big1 = null;
//GC.Collect();
Console.WriteLine("alloc3");
big1 = new BigObject();
Console.WriteLine("done");
Console.Read();
}
}
public class BigObject
{
private const uint OneMeg = 1024 * 1024;
private static int _idCnt;
private readonly int _myId;
private byte[][] _bigArray;
public BigObject()
{
_myId = _idCnt++;
Console.WriteLine("BigObject {0} creating... ", _myId);
_bigArray = new byte[700][];
for (int i = 0; i < 700; i++)
{
_bigArray[i] = new byte[OneMeg];
}
for (int j = 0; j < 700; j++)
{
for (int i = 0; i < OneMeg; i++)
{
_bigArray[j][i] = (byte)i;
}
}
Console.WriteLine("done");
}
~BigObject()
{
Console.WriteLine("BigObject {0} finalised", _myId);
}
}
}
我有一个类 BigObject,它在其构造函数中创建一个 700MiB 数组,并且有一个 finalize 方法,除了打印到控制台之外什么都不做。在 Main 中,我创建了两个这样的对象,释放一个,然后创建第三个。
如果这是为 32 位编译的(以便将内存限制为 2 gigs),则在创建第三个 BigObject 时会抛出内存不足异常。这是因为当第三次请求内存时,无法满足请求,因此垃圾收集器运行。然而,准备好被收集的第一个 BigObject 有一个终结器方法,因此不是被收集,而是被放置在终结队列中并被终结。然后垃圾收集器停止并抛出异常。但是,如果取消注释对 GC.Collect 的调用,或者删除 finalize 方法,代码将运行良好。
我的问题是,为什么垃圾收集器不能尽其所能满足内存请求?如果它运行两次(一次完成,一次释放),上面的代码就可以正常工作。垃圾收集器不应该继续完成和收集,直到在抛出异常之前没有更多的内存可以被释放,并且有没有办法将它配置为这种方式(在代码中或通过 Visual Studio)?