26

OutOfMemoryException我对我们可以使用 try/catch 块捕获一个事实感到有点困惑。

给定以下代码:

Console.WriteLine("Starting");

for (int i = 0; i < 10; i++)
{
    try
    {
        OutOfMemory();
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.ToString());
    } 
}

try
{
    StackOverflow();
}
catch (Exception exception)
{
    Console.WriteLine(exception.ToString());
}

Console.WriteLine("Done");

我用来创建 OutOfMemory + StackOverflowException 的方法:

public static void OutOfMemory()
{
    List<byte[]> data = new List<byte[]>(1500);

    while (true)
    {
        byte[] buffer = new byte[int.MaxValue / 2];

        for (int i = 0; i < buffer.Length; i++)
        {
            buffer[i] = 255;
        }

        data.Add(buffer);
    }
}

static void StackOverflow()
{
    StackOverflow();
}

它打印出OutOfMemoryException10 次,然后由于StackOverflowException无法处理而终止。

RAM 图在执行程序时如下所示: 显示内存被分配和释放 10 次的图表

我现在的问题是为什么我们能够赶上OutOfMemoryException?捕捉到它之后,我们可以继续执行我们想要的任何代码。正如 RAM 图所证明的,有内存被释放。运行时如何知道它可以 GC 哪些对象以及进一步执行仍需要哪些对象?

4

5 回答 5

33

GC 对程序中使用的引用进行分析,并且可以丢弃任何未在任何地方使用的对象。

AnOutOfMemoryException并不意味着内存完全耗尽,它只是意味着内存分配失败。如果你试图一次分配一个大的内存区域,可能仍然有足够的空闲内存。

当没有足够的空闲内存进行分配时,系统会进行垃圾回收以尝试释放内存。如果仍然没有足够的内存来分配,它会抛出异常。

AStackOverflowException无法处理,因为这意味着堆栈已满,并且无法像使用堆一样从中删除任何内容。您将需要更多的堆栈空间来继续运行处理异常的代码,但没有更多的了。

于 2012-12-12T08:51:06.200 回答
6

OutOfMemoryException 很可能会被抛出,因为您正在运行一个 32 位程序,内存图您没有指示系统有多少内存,所以也许尝试将其构建为 64 位,并且可能使用MemoryFailPoint来防止这种情况发生。

您也可以让我们知道 OutOfMemory() 函数中有什么以获得更清晰的画面。

PS StackOverFlow 是唯一无法处理的错误。

编辑:如上所述,我认为这只是合乎逻辑的,因此之前没有提到它,例如,如果您尝试分配比“备用”更多的内存,那么就不可能这样做并且会发生异常。当您使用 data.Add() 分配大型数组时,它会在最终的“非法”添加发生之前倒下,因此仍有可用内存。

所以我会假设它在这一点上 data.Add(buffer); 当您通过向“数据”添加一个 400MB 字节数组来突破 2GB 进程限制时,就会出现问题,例如,一个包含大约 10 亿个对象的数组,每个 4 字节,我预计大约 400MB。

PS 直到 .net 4.5 最大进程内存分配为 2GB,在 4.5 之后可用。

于 2012-12-12T08:48:34.663 回答
0

不确定这是否回答了您的问题,但关于它如何决定要清理哪些对象的(简化)解释是这样的:

垃圾收集器获取程序中每个正在运行的线程并标记所有顶级对象,这意味着可以从堆栈帧访问的所有对象(即,在当前执行点处由局部变量指向的所有对象)以及所有静态字段指向的对象。

然后,它标记下一级对象,即之前标记的对象的所有字段所指向的所有对象。重复此步骤,直到没有新对象被标记。

因为 C# 不允许在普通上下文中使用指针,所以一旦上一步完成,就可以保证后续代码无法访问未标记的对象,因此可以安全地清理。

在您的情况下,如果您分配给内存管理器增加压力的对象没有通过引用保存,这意味着 GC 将有机会清理它们。另外,请记住,OutOfMemoryException 指的是 CLR 程序的托管内存,而 GC 在那个“盒子”之外工作。

于 2012-12-12T08:49:47.783 回答
0

您可以捕获 OutOfMemoryException 的原因是语言设计器决定让您这样做。这有时(但通常不是)实用的原因是因为在某些情况下它是可恢复的情况。

如果您尝试分配一个巨大的数组,您可能会得到一个 OutOfMemoryException,但该巨大数组的内存实际上并没有被分配 - 因此其他代码仍然能够毫无问题地运行。此外,由于异常而展开的堆栈可能会导致其他对象有资格进行垃圾回收,从而进一步增加可用内存量。

于 2012-12-12T08:50:31.887 回答
0

您的方法创建了该方法范围内的本地OutOfMemory()数据结构 (the )。List<byte[]>虽然您的执行线程在OutOfMemory方法内部,但当前堆栈帧被视为列表的 GC 根。一旦您的线程结束在 catch 块中,堆栈帧就会被弹出并且列表实际上变得无法访问。因此,垃圾收集器确定它可以安全地收集列表(正如您在内存图中观察到的那样)。

于 2012-12-12T09:06:48.573 回答