【问题标题】:Detecting when about to run out of memory (getting the amount of "free physical memory")检测何时即将耗尽内存(获取“可用物理内存”的数量)
【发布时间】:2011-05-27 13:35:09
【问题描述】:

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

我想做的是在应用程序耗尽内存之前停止传输。在我的测试中,我发现它与接近于零的“可用物理内存”指标一致。

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

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

有什么想法吗?

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

【问题讨论】:

  • 解决问题怎么样?也许您可以使用固定缓冲区或写入内存映射文件(可以大于物理内存)。
  • 也许有两个缓冲区并交替刷新到磁盘?
  • 这在保存时可以工作,但是我需要内存中的数据来进行内存重播(我无法从普通硬盘驱动器中读取每秒数百兆字节的数据...... )
  • 预览必须是全帧率和分辨率吗?您能否转码为可以更轻松地流回的合理预览大小?
  • 不,必须是全分辨率,甚至可能更高的帧率。

标签: c# memory memory-management


【解决方案1】:

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

人们倾向于将“内存”视为占用芯片上的空间,但十多年来一直没有这种说法。现代操作系统中的内存通常更好地被认为是一个大磁盘文件,它上面有一个大的硬件缓存来加速它。 物理内存只是基于磁盘的内存的性能优化

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

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

【讨论】:

  • 不幸的是,我无法将其转储到磁盘。首先是因为它不够快,而且我需要将它保存在内存中以便重新播放。
【解决方案2】:

我迟到了,但你考虑过使用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% 万无一失,但这是一个开始。由于其他答案中解释的原因,它绝对比性能计数器更可靠。

【讨论】:

  • 非常好!不知道 MemoryFailPoint 类 +1。但是,您的代码中有一个错误:构造函数采用以兆字节为单位的参数,而不是字节。上面的代码要求 30TB 的可用内存!!这肯定会在消费硬件上失败。 :P 不妨指出 “MemoryFailPoint 以 16 MB 的粒度运行。任何小于 16 MB 的值都被视为 16 MB,其他值被视为 16 MB 的下一个最大倍数。” i> 当我在它的时候。
  • 我检查了参考来源,但万一有人担心,没有溢出的风险。 'sizeInMegabytes' 参数在左移 20 位之前被转换为 ulong(这与乘以 1024 * 1024 相同)。
  • 实际上我仔细查看了文档,还有一个错误:MemoryFailPoint 实例应该保持活动状态,直到分配了预期的内存量,然后然后被释放。在单线程场景中,这个错误并不严重,但如果多个线程使用 MemoryFailPoint 检查内存,如果 MemoryFailPoint 实例未放置在正确的位置,它可能会失败(导致 unexpected OutOfMemory)代码。 (MemoryFailPoint 保留进程范围内的保留内存记录,其构造函数递增,Dispose() 递减。)
【解决方案3】:

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

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

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

正如上面的 Tergiver cmets,真正的解决方案是避免将所有文件保存在内存中,而是根据需要在内存中交换图像位。

【讨论】:

    【解决方案4】:

    感谢大家的回答。

    我一直在思考这个问题,并得出一个结论,即我最初想要做的事情将非常困难(如果不是不可能的话),即以某种方式检测应用程序何时将耗尽内存.

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

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

    【讨论】:

      【解决方案5】:

      想添加我自己的答案,因为原本不错的 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-instance,但不是在此之前。 (“尽快”部分是首选但不是关键。延迟处置可能会导致其他内存检查不必要地失败,但至少你会在保守方面犯错。)

      上述要求是需要更改代码设计的。检查内存的方法也必须执行分配,或者必须将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的有趣文章。

      此外,如果您需要分配非常大的数据结构,您需要了解&lt;gcAllowVeryLargeObjects&gt;,您可以在应用配置中设置。


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

      如果您真的想将数据强制存储到物理内存中,那么据我所知,您必须使用 AllocateUserPhysicalPages 进行原生处理,这不是世界上最简单的事情可能出错的事情,需要适当的权限,几乎可以肯定是矫枉过正。操作系统真的不喜欢被告知如何管理内存,所以这样做并不容易......

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          猜你喜欢
          • 2019-07-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-23
          • 2012-11-10
          • 2012-08-19
          • 2011-02-09
          • 2015-10-01
          相关资源
          最近更新 更多