捕获OutOfMemoryException如何工作?

我对使用try / catch块捕获OutOfMemoryException这一事实感到有些困惑。

给出以下代码:

 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 data = new List(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(); } 

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

RAM图在执行程序时看起来像: 图表显示内存被分配和释放10次

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

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

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

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

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

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

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

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

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

所以我会假设它是在这一点data.Add(缓冲区); 当您通过向“数据”添加400MB字节数组来触发2GB进程限制时,在构建数组期间会出现问题,例如,一个大约10亿个对象的数组,每个4字节,我希望大约400MB。

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

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

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

然后,它标记下一级对象,这意味着先前标记的对象的所有字段指向的所有对象。 重复此步骤,直到没有标记新对象。

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

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

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

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

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