C# 捕获 OutOfMemoryException 是如何工作的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13835778/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 09:56:16  来源:igfitidea点击:

How does catching an OutOfMemoryException work?

c#.netclrout-of-memory

提问by GameScripting

I am a little bit confused about the fact that we can just catch an OutOfMemoryExceptionusing a try/catch block.

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

Given the following code:

鉴于以下代码:

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");

The methods I used to create the OutOfMemory + StackOverflowException:

我用来创建 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();
}

It prints out the OutOfMemoryException10 times and then terminates due to the StackOverflowException, which it can't handle.

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

The RAM graph looks like that while executing the program: graph showing that memory gets allocated and released 10 times

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

My question now it why are we able to catch the OutOfMemoryException? After catching it we can just go on execute any code we want. As proven by the RAM graph, there is memory released. How does the runtime know which objects it can GC and which are still required for further execution?

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

采纳答案by Guffa

The GC makes an analysis on the references that are used in the program, and can throw away any object that isn't used anywhere.

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

An OutOfMemoryExceptiondoesn't mean that the memory is completely depleted, it just means that a memory allocation failed. If you tried to allocate a large memory area at once, there may still be plenty of free memory left.

AnOutOfMemoryException并不意味着内存完全耗尽,它只是意味着内存分配失败。如果您尝试一次分配大内存区域,则可能仍有大量可用内存。

When there isn't enough free memory for an allocation, the system does a garbage collection to try to free up memory. If there still isn't enough memory for the allocation, it will throw the exception.

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

A StackOverflowExceptionis not possible to handle, because it means that the stack is full, and it's not possible to remove anything from it as it is with the heap. You would need more stack space to continue running the code that would handle the exception, but there is no more.

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

回答by Paul Zahra

The OutOfMemoryException is quite possibly thrown because you are running a 32 bit program, with the memory graph you have not indicated how much ram the system has, so perhaps try building it as a 64 bit, and maybe use MemoryFailPointto prevent this occurring anyway.

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

You could also let us know what is in the OutOfMemory() function for a clearer picture.

您还可以让我们知道 OutOfMemory() 函数中的内容以获得更清晰的图片。

P.S. StackOverFlow is the only error which cannot be handled.

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

Edit: as mentioned above, and I thought it only logical and hence didn't mention it earlier, if you for example try to allocate more memory than you have 'spare' then it is not possible to do so and an exception occurs. As you are allocating large arrays with your data.Add() it falls over before the final 'illegal' add occurs, hence there is still free memory.

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

So I would assume that it is at this point data.Add(buffer); the issue occurs during the building of the array when you trip the 2GB process limit by adding a 400MB byte array to 'data', e.g. an array of around 1 billion objects at 4 bytes a piece I would expect to be around 400MB.

所以我会假设此时是 data.Add(buffer); 当您通过将 400MB 字节数组添加到“数据”来突破 2GB 进程限制时,问题发生在数组的构建过程中,例如,一个大约 10 亿个对象的数组,每个 4 字节,我预计大约为 400MB。

P.S. Up until .net 4.5 max process memory allocation is 2GB, after 4.5 larger are available.

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

回答by Theodoros Chatzigiannakis

Not sure whether this answers your question, but a (simplified) explanation on how it decides what objects to clean up is this:

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

The garbage collector takes every running thread in your program and marks all top-level objects, which means all objects accessible from the stack frames (that is, all the objects pointed by local variables at the points where execution currently is) as well as all objects pointed by static fields.

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

Then, it marks the next level objects, which means all the objects pointed by all the fields of the previously marked objects. This step is repeated until no new objects are marked.

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

Because C# doesn't allow pointers in the normal context, once the previous step is completed, it's guaranteed that the non-marked objects are not accessible by subsequent code and therefore can be cleaned up safely.

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

In your case, if the objects you have allocated to add pressure to the memory manager are not kept by reference, it means that the GC will have the chance to clean them up. Also, keep in mind that OutOfMemoryException refers to the managed memory of your CLR program, while the GC works a little outside of that "box".

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

回答by Matthew Watson

The reason you can catch an OutOfMemoryException is because the language designer decided to let you. The reason this is sometimes (but not usually) practical is because it's a recoverable situation in some cases.

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

If you attempt to allocate a huge array, you may get an OutOfMemoryException, but the memory for that huge array will not actually have been allocated - so other code will still be able to run without problems. Also, the stack unwinding due to the exception may cause other objects to be come eligible for garbage collection, further increasing the amount of memory available.

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

回答by afrischke

Your OutOfMemory()method creates data structure (the List<byte[]>) that is local to the method's scope. While your thread of execution is inside the OutOfMemorymethod, the current stack frame is considered the GC root for the List. Once your thread ends up in the catch block, the stack frame has been popped and the list has effectively become unreachable. Therefore the garbage collector determines that it can safely collect the list (which it does as you have observed in your memory graph).

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