如何在.Net中获取OutOfMemoryException?

时间:2012-03-07 23:08:00

标签: .net memory out-of-memory virtual-memory

在我正在开发的内存饥渴的应用程序中,我正面临OutOfMemoryExceptions。我希望虚拟内存永远不会有这种异常。为什么如果需要更多RAM而不是可用操作系统没有将HD用作RAM?

2 个答案:

答案 0 :(得分:10)

您可以通过多种方式获取OutOfMemoryException。在32位窗口中,进程可以使用2GB的虚拟地址空间(我相信它在64位应用程序中是1TB)。请记住,这是而不是 RAM,它完全不同,因此是“虚拟”。所以忘了RAM。 RAM所做的就是让事情变得更快。 Windows内存管理器决定程序使用的内存是在RAM还是磁盘中。当你打开许多程序时,较少的内存被加载到物理RAM中,而更多的内存将被加载到磁盘上。你的表现会很糟糕,但你不会有OOM例外。

在.NET中获取OOM异常的最常见方法是尝试分配足够大量的数据(类似于字节数组),其中没有足够的连续内存页面可以映射它,这是我怀疑你的问题所在。这意味着即使您正在请求一个将虚拟地址空间增加到2GB以上的新对象,也可能会出现内存不足异常。

“但等等!”,有人可能会说,“当垃圾收集器运行时,内存会被压缩,所以堆末端总会有连续的空间!” EM>

大多数情况下都是如此。 GC将从堆中收集,它实际上可以被认为是两个堆,小对象堆(SOH)和来自大对象堆(LOH)的堆。大多数对象,因为它们相当小,将存在于主要的世代堆中。但是,如果对象高于某个大小阈值(这是85K,但可能已经改变),则将其分配给LOH。

当从LOH回收内存时,不像压缩。对于LOH中的存储器来说,压缩是太昂贵了。内存分配虽然足够昂贵,但应用程序性能会受到影响。这意味着您的堆可以并且经常变得支离破碎(在.NET 4.0运行时,与2.0兼容版本相比,这已大大改进)。

  • 假设您的LOH堆在GC之前看起来像这样:
* = object
- = free space

|**|***|*|**|*|******|****|**|*****|*******|**|*|***|*****|***|**|--
 ^        ^  ^                ^             ^        ^         ^  marked for GC
  • 一旦完成完整的 GC,它就会像这样:
* = object
- = free space

|--|***|*|-- -|******|****|**|-----|*******|--|*|***|-----|***|----

现在我想分配一个数组********。运行时将尝试在其中找到适合的位置。它没有。运行时有什么作用?好吧,假设上面代表你的整个2GB虚拟空间并且不能分配更多内存,将抛出OOM异常。您拥有内存的免费页面,但不足以连续

我不知道你的应用程序做了什么,但这里有几件事要检查:

  • 您是否在静态类型成员中分配大型数组?只要静态成员指向该对象,即使您不再使用它,它也永远不会是GC。静态成员存在于类型对象上,只有在卸载AppDomain时才会释放它们。

  • 您是否根据用户或其他输入获得分配大小?例如,如果您正在读取一个告诉您条目长度的文件头,您是否验证条目的大小是否正确?我自己遇到了一个错误,我在一个文件中读错了地址并试图分配2TB的内存。验证这种疯狂。

  • 如果要分配大型数组,真的需要吗?将数据加载到内存并不能保证您的性能会提高。请记住,您的程序是分配虚拟内存空间,而不是ram。如果您正在阅读文件,您是在挑选并选择您完全阅读哪些内容以及您正在播放的内容?

我能想到的只有一些事情,而且肯定不全面。除了尝试处理超过2GB的地址空间之外,耗尽内存的另一种方法是整页文件,但这种情况不太常见。

一些有用的工具:

  • WinDbg(包含在Windows SDK中,作为Windows调试工具的一部分) - 您可以使用它来查看有关堆的统计信息。我最喜欢的是!dumpheap -stat!dumpheap -type [type]。我可以使用它来快速查找使用大量内存的类型。

  • CLRProfiler - 比WinDbg更易于使用,功能更少。它为您提供了很好的内存分配图形,您甚至可以看到应用程序退出时剩余的GC句柄数。图表按类型着色,因此它概述了堆中使用最多内存的类型。

答案 1 :(得分:0)

如果超过最大堆大小或地址空间,则可以获得此值。