即使有可用内存,MemoryFailPoint始终会抛出InsufficientMemoryException异常。

10

我已经编写了以下代码来检查内存是否充足:

while (true)
{
    try
    {
        // Check for available memory.
        memFailPoint = new MemoryFailPoint(250);

        break;
    }
    catch (InsufficientMemoryException ex)
    {
        if (memFailPoint != null)
        {
          memFailPoint.Dispose();
        }

        Thread.Sleep(waitSecond * 1000);
    }
}

我正在Windows 7 64位计算机上的控制台应用程序中运行以上内容。

每10秒钟有4个调用该方法。

起初它运行良好,但在2-3个小时后,总是会抛出InsufficientMemoryException异常。我检查了可用内存,显示超过1 GB。

我尝试了很多次,但无法找到此问题的原因。

以下是堆栈跟踪:

at System.Runtime.MemoryFailPoint..ctor(Int32 sizeInMegabytes)
at SocketListner.AcceptConnection(IAsyncResult res) in H:\Projects\SocketListner.cs:line 308

没有内部异常。


你还做了什么来引发这个错误?你能提供一个堆栈跟踪吗? - DJ Burb
我已经添加了堆栈跟踪,请重新查看问题。 - Imran Rizvi
4
"我检查了可用内存" 如何检查?即使有1GB的空闲内存,它可能过于碎片化,无法分配250兆的空间。要成功地进行此操作,需要在内存中有连续的250M空闲空间。 - spender
需要分配这么大的内存空间,这是一个巨大的数量。它是否真正反映了您的分配需求?您需要分配一个非常大的数组才能反映出如此大的分配量。如果您打算创建许多较小的对象,则测试250兆字节的方法是不好的。 - spender
我认为是的,一旦数据包被处理,内存应该被释放,但是经过长时间后,数据库会成为瓶颈,数据会在内存中保留直到超时,即30秒。 - Imran Rizvi
显示剩余4条评论
3个回答

16

你可以信赖这个方法正常工作,但当你请求250兆字节时,在32位进程中出现这种异常的可能性非常高。当程序运行一段时间后获得这么大的空间变得比较困难。

一个程序永远不会因为消耗所有可用的虚拟内存地址空间而崩溃。它会崩溃是因为剩余的地址空间中没有足够大的空洞来容纳所需的分配。你的代码请求一次性分配250兆字节的大空洞,如果没有得到异常,你可以确信这个分配不会失败。

但是250兆字节相当多,这是一个非常大的数组。由于一个叫做“地址空间碎片”的问题,这很可能失败。换句话说,程序通常会开始有几个非常大的空洞,最大的约为600兆字节。这些空洞是在为存储由.NET运行时和未管理的Windows DLL使用的代码和数据而进行的分配之间可用的。随着程序分配更多的内存,这些空洞变得越来越小。它可能会释放一些内存,但这并不能产生一个大的空洞。通常情况下,你会得到两个空洞,大约是原始空洞的一半大小,在中间分配一个内存块将原始的大空洞分为两部分。

这被称为碎片化,一个分配和释放大量内存的32位进程最终会破坏虚拟内存地址空间,因此在一段时间后仍然可用的最大空洞变得越来越小,大约是90兆字节。请求250兆字节几乎肯定会失败。你需要降低请求的内存。

您毫无疑问希望它能以不同的方式工作,确保添加起来达到250兆字节的分配总和得到保障。然而,MemoryFailPoint并不是这样工作的,它只检查最大可能的分配。可以毫不客气地说,这使其变得不太有用。我对.NET框架程序员表示同情,让它按我们所希望的方式工作既昂贵又无法提供保证,因为分配的大小最为重要。

虚拟内存是一种丰富的资源,价格非常便宜。但是接近消耗它所有资源非常麻烦。一旦您消耗了1GB,则OOM在随机时间开始变得可能。不要忘记解决此问题的简单方法,您正在运行64位操作系统。所以只需将EXE平台目标更改为AnyCPU即可获得大量的虚拟地址空间。根据操作系统版本,可能达到1TB。它仍然会碎片化,但您再也不必担心了,空洞很大。

最后但并非最不重要的是,在评论中可见,此问题与RAM没有任何关系。虚拟内存与您拥有多少RAM完全无关。将虚拟内存地址映射到RAM中的物理地址是操作系统的工作,它会动态地这样做。访问内存位置可能会触发页面错误,操作系统将为该页面分配RAM。反之亦然,当需要在其他地方使用时,操作系统将取消映射页面的RAM。您永远无法耗尽RAM,机器在此之前会变得非常缓慢。SysInternals' VMMap实用程序对于查看您的程序的虚拟地址空间很有用,尽管您可能会淹没在大型进程的信息中。


感谢您的详细分析和建议。我已将大小减小到50MB并将应用程序放入测试中,但是我制作了64位构建,因此不应再出现OOM异常,因为它早已被捕获。 - Imran Rizvi
我必须说,仅仅将其从250减少到50是相当随意的。选择的数字必须具有一定的真实性,并代表您将需要的内存量。 - Hans Passant
如何知道呢?一旦数据包被处理,内存应该被释放,但是经过长时间后,数据库成为瓶颈,数据会在内存中保留到超时,即30秒。 - Imran Rizvi
1
如果你不知道该怎么做,那么使用内存分析器收集证据是很重要的。我提到的VMMap实用程序就是其中一种廉价版本。尝试调整传递给MemoryFailPoint的值是达到目的的冗长方式。 - Hans Passant

0

在调用之前和之后,考虑使用GC.GetTotalMemory方法来确定可用内存的数量:

memFailPoint = new MemoryFailPoint(250);

InsufficientMemoryException 是在开始操作之前由 MemoryFailPoint 构造函数抛出的,当您指定的预计内存分配大于当前可用内存的数量时。

就像 user7116 评论 的那样,这就是为什么您应该先检查。

此链接中的示例应该会给您一个解决方案:MemoryFailPoint Class

您还可以查看此 MSDN 博客文章:内存不足?增加程序可用内存的简单方法


0

MemoryFailPoint检查连续可用内存,如此文档所述:http://msdn.microsoft.com/fr-fr/library/system.runtime.memoryfailpoint.aspx

你可能只使用了很少的内存,但是已经将其分散了很多,现在无法分配所需大小的连续内存块。这种问题很容易在几个小时后发生。为了避免这种情况,请为您不断实例化的对象使用对象池,它将使正在使用的内存空间更加稳定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接