检测即将耗尽的内存(获取“免费物理内存”的数量)

时间:2010-12-18 13:23:45

标签: c# memory memory-management

我正在将图像从高FPS相机传输到内存缓冲区(列表),并且由于这些图像非常大,因此计算机的内存耗尽非常快。

我想要做的是在应用程序内存不足之前停止传输。在我的测试过程中,我发现它与“Free Physical Memory”指标一致,接近于零。

现在问题是我找不到以编程方式获取此值的方法;在XP中,它甚至没有显示在任何地方(仅在Vista / 7任务管理器中)。

alt text

我已经尝试了所有可以找到的方法(WMI,性能计数器,MemoryStatus,......),但我从中获得的只是“可用物理内存”,当然不一样。

有什么想法吗?

更新 不幸的是,我需要数据在内存中(是的,我知道我不能保证它将在物理内存中,但仍然),因为数据是实时流式传输的,我需要在存储它之后在内存中预览它。

6 个答案:

答案 0 :(得分:9)

相关性不是因果关系。即使负载物理内存仍然空闲,您也可以“耗尽内存”。物理记忆几乎肯定无关紧要;您可能用完的是地址空间

人们倾向于将“记忆”视为消耗芯片上的空间,但十多年来情况并非如此。现代操作系统中的内存通常被认为是一个大型磁盘文件,其上面有一个大硬件缓存以加快速度。 物理内存只是基于磁盘的内存的性能优化

如果你的物理内存耗尽,那么你的表现会很糟糕。但是稀缺的资源实际上是你正在耗尽的地址空间。一个大的列表必须有一个大的连续的地址空间块,并且可能没有任何大小足够大的块你想要的大小。

不要那样做。下拉一个合理大小的块,将其转储到磁盘,然后根据需要处理磁盘上的文件。

答案 1 :(得分:7)

我迟到了,但你考虑过使用System.Runtime.MemoryFailPoint课了吗?它做了很多事情来确保请求的分配成功,如果失败则抛出InsufficientMemoryException;你可以抓住这个并停止转移。您可以预测传入帧的平均大小,并尝试分配3或4个帧,然后在发生故障时停止采集。也许是这样的?

const int AverageFrameSize = 10 * 1024 * 1024 // 10MB

void Image_OnAcquired(...)
{
    try
    {
        var memoryCheck = new MemoryFailPoint(AverageFrameSize * 3);
    }
    catch (InsufficientMemoryException ex)
    {
        _camera.StopAcquisition();
        StartWaitingForFreeMemory();
        return;
    }

    // if you get here, there's enough memory for at least a few
    // more frames
}

我怀疑它是100%万无一失,但这是一个开始。由于其他答案中解释的原因,它肯定比性能计数器更可靠。

答案 2 :(得分:2)

您不能在Vista / 7中单独使用空闲内存计数器作为指南,因为它可能始终接近零 。原因是Vista / 7的superfetch使用可用内存来缓存它认为你可能会使用的磁盘内容。

Linky:http://www.codinghorror.com/blog/2006/09/why-does-vista-use-all-my-memory.html

此外,如果你正在运行一个32位的C#进程,你每个进程的内存限制为2GB(实际上在事情变得不稳定之前更像是1.5GB)所以即使你的盒子显示你有大量的免费当你的进程达到2GB限制时,你仍然会得到一个内存不足的异常。

正如Tergiver上面评论的那样,真正的解决方案是避免将所有文件保存在内存中,而是根据需要将图像的比特交换进出内存。

答案 3 :(得分:1)

感谢所有答案。

我一直在考虑它并且得出结论,做我最初想要的事情是非常困难的(如果不是不可能的话),那就是以某种方式检测应用程序何时会耗尽内存

所有答案似乎指向相同的方向(以某种方式将数据保存在内存中),但遗憾的是我不能去那里,因为我真的“需要”将数据保留在内存中(如果可能的话,物理上)。

由于我必须做出妥协,我决定为用户创建一个设置来决定捕获数据的内存使用限制。它至少很容易实现。

答案 4 :(得分:1)

想要添加我自己的答案,因为好的answer by OwenP在使用System.Runtime.MemoryFailPoint的方式上有两个重要错误。

第一个错误是一个非常简单的修复:构造函数签名是public MemoryFailPoint(int sizeInMegabytes)所以AverageFrameSize参数应该是兆字节而不是字节。另请注意以下大小:

  

MemoryFailPoint的粒度为16 MB。任何小于16 MB的值都被视为16 MB,其他值被视为16 MB的下一个最大倍数。

第二个错误是MemoryFailPoint实例必须保持活动状态,直到你想要使用的内存被分配后,然后处理掉!

这可能有点难以修复,可能需要根据OP的实际代码来进行设计更改。

您必须以这种方式处理它的原因是MemoryFailPoint类保留了由其构造函数创建的进程范围内存保留记录。这样做是为了确保如果两个线程几乎同时执行内存检查,除非有足够的内存来满足两个线程的需求,否则它们都不会成功。 (否则MemoryFailPoint类在多线程应用程序中将无用!)

当调用Dispose()时,构造函数“保留”的内存是未保留的。因此,线程应该在分配了所需的内存之前之后尽快处理MemoryFailPoint - 实例。。 (“尽快”部分是首选但不是关键。延迟处置会导致其他内存检查失败,但至少你在保守派方面犯了错误。)

上述要求是需要改变代码设计的要求。检查内存的方法还必须执行分配,或者必须将MemoryFailPoint实例传递给调用者,这使得调用者有责任在正确的时间处置它。 (后者是MSDN上的示例代码所做的。)

使用第一种方法(和固定的缓冲区大小)可能如下所示:

const int FrameSizeInMegabytes = 10; // 10MB (perhaps more is needed?)
const int FrameSizeInBytes = FrameSizeInMegabytes << 20;
// shifting by 20 is the same as multiplying with 1024 * 1024.

bool TryCreateImageBuffer(int numberOfImages, out byte[,] imageBuffer)
{
    // check that it is theoretically possible to allocate the array.
    if (numberOfImages  < 0 || numberOfImages > 0x7FFFFFC7)
        throw new ArgumentOutOfRangeException("numberOfImages",
            "Outside allowed range: 0 <= numberOfImages <= 0x7FFFFFC7");

    // check that we have enough memory to allocate the array.
    MemoryFailPoint memoryReservation = null;
    try
    {
        memoryReservation =
            new MemoryFailPoint(FrameSizeInMegabytes * numberOfImages);
    }
    catch (InsufficientMemoryException ex)
    {
        imageBuffer = null;
        return false;
    }

    // if you get here, there's likely to be enough memory
    // available to create the buffer. Normally we can't be
    // 100% sure because another thread might allocate memory
    // without first reserving it with MemoryFailPoint in
    // which case you have a race condition for the allocate.
    // Because of this the allocation should be done as soon
    // as possible - the longer we wait the higher the risk.
    imageBuffer = new byte[numberOfImages, FrameSizeInBytes];

    //Now that we have allocated the memory we can go ahead and call dispose
    memoryReservation.Dispose();

    return true;
}

0x7FFFFFC7是单字节类型数组上任何维度允许的最大索引器,可以在MSDN page about arrays上找到。

第二种方法(调用方负责MemoryFailPoint实例)可能如下所示:

const int AverageFrameSizeInMegabytes = 10; // 10MB

/// <summary>
/// Tries to create a MemoryFailPoint instance for enough megabytes to
/// hold as many images as specified by <paramref name="numberOfImages"/>.
/// </summary>
/// <returns>
/// A MemoryFailPoint instance if the requested amount of memory was
/// available (at the time of this call), otherwise null.
/// </returns>
MemoryFailPoint GetMemoryFailPointFor(int numberOfImages)
{
    MemoryFailPoint memoryReservation = null;
    try
    {
        memoryReservation =
            new MemoryFailPoint(AverageFrameSizeInMegabytes * numberOfImages);
    }
    catch (InsufficientMemoryException ex)
    {
        return null;
    }
    return memoryReservation;
}

这看起来更简单(并且更灵活),但现在由调用者来处理MemoryFailPoint实例并在正确的时间点处理它。 (添加了一些必修文档,因为我没有为该方法提供一个好的描述性名称。)

重要提示:“保留”在此上下文中的含义

内存不是“保留”的,因为保证可用(对于调用线程)。它只表示当一个线程使用MemoryFailPoint来检查内存时,假设它成功,它会将它的内存大小添加到MemoryFailPoint类跟踪的进程范围(静态)“保留”数量。此预留将导致对MemoryFailPoint的任何其他呼叫(例如,来自其他线程)将可用内存的总量视为实际量减去当前进程范围(静态)“保留”量。 (当处置MemoryFailPoint个实例时,它们会从保留的总数中减去它们的数量。)然而,实际的内存分配系统本身并不知道或不关心这个所谓的“预留”,这是MemoryFailPoint没有强有力保证的原因之一。

另请注意,记忆“保留”只是作为一个数量跟踪。由于它不是特定内存段的实际预留,因此进一步削弱了保证,如参考源中的以下令人沮丧的评论所示:

  

// Note that multiple threads can still ---- on our free chunk of address space, which can't be easily solved.

猜测被删除的单词是什么并不难。

这是一篇关于how to overcome the 2GB limit on arrays的有趣文章。

此外,如果您需要分配非常大的数据结构,则需要了解可以在app-config中设置的<gcAllowVeryLargeObjects>

值得一提的是,这与OP真正想要的物理内存没有任何关系。事实上,MemoryFailPoint在放弃并报告失败之前会尝试做的事情之一就是增加页面文件的大小。但如果正确使用它会避免获得OutOfMemoryException,这将是一个非常体面的工作,这至少是OP想要的一半。

如果确实想要将数据强制插入物理内存,那么据我所知,你必须使用AllocateUserPhysicalPages本地生成,这不是世界上最简单的事情。过多的事情可能会出错,需要适当的权限,几乎肯定是矫枉过正。操作系统真的不想被告知如何管理内存,因此不容易这样做......

答案 5 :(得分:0)

获取OutOfMemoryException只意味着无法遵守当前的内存分配。它并不一定意味着系统甚至进程内存不足。想象一下一个hello world类型的应用程序,它首先分配一个2 GB的内存块。在32位系统上,尽管该进程此时尚未真正分配任何重要内存,但很可能会触发异常。

OutOfMemoryExceptions的常见来源是没有足够的连续内存可用。即有足够的内存,但没有大块足以满足当前的请求。换句话说,通过观察空闲内存计数器来避免OOM实际上并不可行。