【问题标题】:OutOfMemoryException when creating multiple byte arrays创建多个字节数组时出现 OutOfMemoryException
【发布时间】:2012-04-11 00:10:25
【问题描述】:

我在一个创建和处理一些字节数组的方法中经常遇到OutOfMemoryException。代码如下所示:

  1. 创建 MemoryStream 以获取一些数据(大约 60MB)。
  2. 创建字节数组(与MemoryStream大小相同,约60MB)
  3. 用内存流中的字节填充数组
  4. 关闭 MemoryStream
  5. 处理字节数组中的数据
  6. 离开方法

当这个方法被调用 20-30 次时,我得到 OutOfMemoryException 就在分配字节数组的位置。但我不认为这是系统内存问题。应用程序内存使用量约为 500MB(私有工作集),测试机器为 64 位,4GB 内存。

方法完成后字节数组或MemoryStream使用的内存是否可能没有释放?但是,看起来这块内存并没有分配给进程,因为私有工作集只有 500MB 左右。

除了物理内存不足之外,创建大字节数组(60MB)时,OutOfMemoryException 的原因是什么?

[编辑添加代码示例] 来源来自PdfSharp lib

byte[] imageBits = new byte[streamLength]; 行抛出异常,看起来确实像 LOH 碎片问题。

/// <summary>
/// Reads images that are returned from GDI+ without color palette.
/// </summary>
/// <param name="components">4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB)</param>
/// <param name="bits">8</param>
/// <param name="hasAlpha">true (ARGB), false (RGB)</param>
private void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
{
  int pdfVersion = Owner.Version;
  MemoryStream memory = new MemoryStream();
  image.gdiImage.Save(memory, ImageFormat.Bmp);
  int streamLength = (int)memory.Length;

  if (streamLength > 0)
  {
    byte[] imageBits = new byte[streamLength];
    memory.Seek(0, SeekOrigin.Begin);
    memory.Read(imageBits, 0, streamLength);
    memory.Close();

    int height = image.PixelHeight;
    int width = image.PixelWidth;

    if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
        ReadDWord(imageBits, 2) != streamLength ||
        ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER
        ReadDWord(imageBits, 18) != width ||
        ReadDWord(imageBits, 22) != height)
    {
      throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format");
    }
    if (ReadWord(imageBits, 26) != 1 ||
      (!hasAlpha && ReadWord(imageBits, 28) != components * bits ||
       hasAlpha && ReadWord(imageBits, 28) != (components + 1) * bits) ||
      ReadDWord(imageBits, 30) != 0)
    {
      throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #2");
    }

    int nFileOffset = ReadDWord(imageBits, 10);
    int logicalComponents = components;
    if (components == 4)
      logicalComponents = 3;

    byte[] imageData = new byte[components * width * height];

    bool hasMask = false;
    bool hasAlphaMask = false;
    byte[] alphaMask = hasAlpha ? new byte[width * height] : null;
    MonochromeMask mask = hasAlpha ?
      new MonochromeMask(width, height) : null;

    int nOffsetRead = 0;
    if (logicalComponents == 3)
    {
      for (int y = 0; y < height; ++y)
      {
        int nOffsetWrite = 3 * (height - 1 - y) * width;
        int nOffsetWriteAlpha = 0;
        if (hasAlpha)
        {
          mask.StartLine(y);
          nOffsetWriteAlpha = (height - 1 - y) * width;
        }

        for (int x = 0; x < width; ++x)
        {
          imageData[nOffsetWrite] = imageBits[nFileOffset + nOffsetRead + 2];
          imageData[nOffsetWrite + 1] = imageBits[nFileOffset + nOffsetRead + 1];
          imageData[nOffsetWrite + 2] = imageBits[nFileOffset + nOffsetRead];
          if (hasAlpha)
          {
            mask.AddPel(imageBits[nFileOffset + nOffsetRead + 3]);
            alphaMask[nOffsetWriteAlpha] = imageBits[nFileOffset + nOffsetRead + 3];
            if (!hasMask || !hasAlphaMask)
            {
              if (imageBits[nFileOffset + nOffsetRead + 3] != 255)
              {
                hasMask = true;
                if (imageBits[nFileOffset + nOffsetRead + 3] != 0)
                  hasAlphaMask = true;
              }
            }
            ++nOffsetWriteAlpha;
          }
          nOffsetRead += hasAlpha ? 4 : components;
          nOffsetWrite += 3;
        }
        nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary
      }
    }
    else if (components == 1)
    {
      // Grayscale
      throw new NotImplementedException("Image format not supported (grayscales).");
    }

    FlateDecode fd = new FlateDecode();
    if (hasMask)
    {
      // monochrome mask is either sufficient or
      // provided for compatibility with older reader versions
      byte[] maskDataCompressed = fd.Encode(mask.MaskData);
      PdfDictionary pdfMask = new PdfDictionary(document);
      pdfMask.Elements.SetName(Keys.Type, "/XObject");
      pdfMask.Elements.SetName(Keys.Subtype, "/Image");

      Owner.irefTable.Add(pdfMask);
      pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
      pdfMask.Elements[Keys.Length] = new PdfInteger(maskDataCompressed.Length);
      pdfMask.Elements[Keys.Filter] = new PdfName("/FlateDecode");
      pdfMask.Elements[Keys.Width] = new PdfInteger(width);
      pdfMask.Elements[Keys.Height] = new PdfInteger(height);
      pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
      pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true);
      Elements[Keys.Mask] = pdfMask.Reference;
    }
    if (hasMask && hasAlphaMask && pdfVersion >= 14)
    {
      // The image provides an alpha mask (requires Arcrobat 5.0 or higher)
      byte[] alphaMaskCompressed = fd.Encode(alphaMask);
      PdfDictionary smask = new PdfDictionary(document);
      smask.Elements.SetName(Keys.Type, "/XObject");
      smask.Elements.SetName(Keys.Subtype, "/Image");

      Owner.irefTable.Add(smask);
      smask.Stream = new PdfStream(alphaMaskCompressed, smask);
      smask.Elements[Keys.Length] = new PdfInteger(alphaMaskCompressed.Length);
      smask.Elements[Keys.Filter] = new PdfName("/FlateDecode");
      smask.Elements[Keys.Width] = new PdfInteger(width);
      smask.Elements[Keys.Height] = new PdfInteger(height);
      smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
      smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
      Elements[Keys.SMask] = smask.Reference;
    }

    byte[] imageDataCompressed = fd.Encode(imageData);

    Stream = new PdfStream(imageDataCompressed, this);
    Elements[Keys.Length] = new PdfInteger(imageDataCompressed.Length);
    Elements[Keys.Filter] = new PdfName("/FlateDecode");
    Elements[Keys.Width] = new PdfInteger(width);
    Elements[Keys.Height] = new PdfInteger(height);
    Elements[Keys.BitsPerComponent] = new PdfInteger(8);
    // TODO: CMYK
    Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB");
    if (image.Interpolate)
      Elements[Keys.Interpolate] = PdfBoolean.True;
  }
}

【问题讨论】:

  • #1 问题,您是否正确处理您的流?此外,您可能需要提供一些代码。
  • @CodingGorilla 除了 MemoryStream 之外没有什么可以处理的。哪个正在关闭。事实上,MemoryStream.Close() 只是对 MemoryStream.Dispose(true) 的调用,然后是 GC.SuppressFinalize(this)。字节数组不是一次性的,所以我无能为力。至于代码......它非常复杂,来自 PdfSharp lib。这不是我的代码,但我正在尝试理解并修复它呈现的问题。
  • 我正在阅读一些碎片问题。似乎在 64 位窗口上,底层内存管理器不应该公开这种行为。您的进程是否有可能在 WOW64 中运行,即您仅针对 x86 编译?我用关于碎片问题的更多信息更新了我的答案。
  • 这里有很多副本和辅助数组。这可能是一种罕见的情况,调用 GC.Collect() 可能会有所帮助,最好是 afterReadTrueColorMemoryBitmap() 的末尾。
  • 我们都同意问题在于 GC 未能恢复或分割托管内存堆。然后,一个不错的选择可能是使用非托管内存:System.IO.UnmanagedMemoryStream 问题是您必须事先知道所需的空间。或者,至少,有一个上限。文档清楚地表明此流不会在堆上分配内存。另一个问题是您的程序需要允许这样做的安全设置。

标签: c# .net out-of-memory pdfsharp


【解决方案1】:

您的堆内存正在抛出此异常,请尝试在最后调用 GC.Collect() 以释放资源。也可以MemoryProfiler查看堆内存使用情况,自带14天试用

【讨论】:

    【解决方案2】:

    我希望您使用的是MemoryStream.GetBuffer(),并且您没有复制到新数组。

    您的主要问题不是直接缺少内存,而是LOH 的碎片。这可能是一个棘手的问题,主要问题是分配不同大小的大型缓冲区。 LOH 上的项目已收集但未压缩。

    解决方案可能是:

    • 首先确保您没有阻止收集任何内容。使用探查器。
    • 尝试重复使用您的缓冲区。
    • 四舍五入分配向上到一组固定数字。

    最后两个都要求您使用超大数组,可能需要一些工作。

    【讨论】:

    • 不幸的是,代码作者确实将 MemoryStream 复制到了字节数组中。
    • @SiliconMind:想到的一件事是使用一个小的内存流并只将小块写入目标字节数组,这可以防止两个大数组中的至少一个存在并保存一个LOH 碎片很多。
    • 我已经尝试过 MemoryStream.GetBuffer() ,这似乎有点帮助,但只是在某种程度上。重用缓冲区几乎是不可能的,因为数据可能小到几个字节,大到……非常大。该代码处理可能是 1x1 像素或 10000x10000 像素大的位图。无论如何,这似乎确实是 LOH 问题。让我担心的是,调用 GC.Collect() 并不能解决问题,迟早会抛出 OutOfMemoryException。
    • 您可以更改代码吗?你真的需要发布(一些)它以获得更好的答案。
    • 接受这个答案,因为它似乎确实是 LOH 碎片问题。使用 MemoryStream.GetBuffer() 会有所帮助,但可以改用 Bitmap.LockBits 并获得更好的结果。
    【解决方案3】:

    尝试将其包含在 using(MemoryStream x = ...) { } 块中,这将为您处理该对象。

    虽然 Close 应该是 Dispose 对象,但根据 .NET 指南,MemoryStream 可能有所不同。

    【讨论】:

    • 在较早的(现已删除)nonnb 的回答中,有人建议这样做。然而,正如 Jon Skeet 在那里评论的那样,处理 MemoryStream 并不会释放缓冲区(它只是将 Open 和 Writable 标志设置为 false)。 GC 需要启动以释放字节数组。
    • 当然。您应该让 GC 处理您的内存,或者不要太频繁地使用 GC.Collect()。但是 using 块保证流被释放,即使出现问题(即抛出异常,你忘记关闭/处置对象)。
    • 我同意,但在 MemoryStream 的情况下,这根本不是真的。 MS 拥有的唯一资源是字节数组,没有非托管资源。当对象是一次性的时,最好使用using{..},但在某些情况下,一个对象只是IDisposable,因为继承链需要它。在这种情况下,Dispose() 实际上是一个空操作并且不会释放资源。如果您不相信我的说法,请检查 Reflector ;)。注意:在这里使用using 并没有什么坏处,但它也没有任何作用。
    • 感谢您的评论。你完全正确。在许多情况下它并不重要,但我相信你同意它只是有一点帮助,因为在离开 using 块范围时对对象的引用会消失,因此它可以被 GC 收集。这将发生在任何类型的范围内(for body 或其他)。在创建许多流并且没有超出范围的情况下,这可能会很有用。如果我错了,请纠正我。
    【解决方案4】:

    首先,不建议您通过 TSQL 读取/写入大型 FILESTREAM 数据。推荐的方法是使用 SQL server 提供的 Win32/DOTNET API。我在上面发布的代码显示了如何使用 SqlFileStream() .net 类访问 FILESTREAM 数据。它还展示了如何以更小的块发送数据。

    如果您的总内存足够,您可以通过创建一堆较小的数组并将它们包装在单个 IList 或其他一些索引接口中来防止 LOH 碎片导致的内存不足异常。

    【讨论】:

    • 不是 SQL 相关的问题。没有 SQL。你在说什么?
    【解决方案5】:

    已建议处理,但仅限于MemoryStream,这对您无济于事。此外,建议使用GC.Collect。这可能无济于事,因为您的记忆似乎没有大幅增长。请注意,调用 GC.Collect 可能是一项昂贵的操作。

    碎片化

    看起来您确实遇到了臭名昭著的大对象堆碎片问题。这很可能是由于经常分配和释放 60MB 内存块造成的。如果 LOH 碎片化,它就会保持碎片化。这是长期运行的 .NET 应用程序的一个主要问题,也是 ASP.NET 经常配置为间隔重新启动的原因。

    防止 OutOfMemoryException

    请参阅上面的 CodeProject 文章了解如何执行此操作。诀窍是,使用MemoryFailPoint 并抓住InsufficientMemoryException。这样,您可以优雅地降级并且您的应用程序不会变得不稳定。

    可能的通用解决方案

    确保您的大型对象尽可能长地存在。重复使用缓冲区。以足够的大小分配一次,并在再次需要时将缓冲区归零。这样您就不会遇到任何其他内存问题。当您的对象大小保持在 85k 以下时,它们通常不会进入 LOH 并且不会混乱。

    64位机器应该没有这个问题

    编辑:根据this post(解决方法选项卡)和this post(见公开评论),这个问题不应该出现在 64 位机器上。既然你说你在 64 位机器上运行你的代码,也许你是用设置为 x86 的配置编译的?

    【讨论】:

    • 虽然我的测试机是64位的,但不幸的是目标平台是32位的WinXP。似乎 CodeProject 上描述的问题很久以前就已经用 MS 的补丁修复了。
    • @SiliconMind:不,问题没有解决。只是在 64 位模式下,由于更大的虚拟地址空间(不是物理地址空间),它的发生频率要低得多。为什么你的目标是 32 位?如果您测试机器是 64 位并运行 Windows 7,则为 64 位构建您的项目,看看它是否有所作为。
    【解决方案6】:

    我在下面的代码中遇到了同样的问题:

    ImageData = (byte[])pDataReader.qDataTable.Rows[0][11];
    if (ImageData != null)
    {
        ms = new MemoryStream(ImageData);
        button1.BackgroundImage = Image.FromStream(ms);
    }
    ImageData = null;
    
    ImageData = (byte[])pDataReader.qDataTable.Rows[0][12];
    if (ImageData != null)
    {
        ms = new MemoryStream(ImageData);
        button1.BackgroundImage = Image.FromStream(ms);
    }
    ImageData = null;
    ms.Close();
    

    删除ms.Close(); 即可解决问题。
    我认为问题的出现是因为您在 if 块之外定义了 MemoryStream memory = new MemoryStream(); 并在 if 块中将其关闭,和我一样!

    【讨论】:

    • 关闭未分配的流时是否真的出现“内存不足”异常?问题中的代码总是在“if”之前创建并填充流,因此我看不到您的“答案”与原始问题的任何相关性。
    • @Alessio Cantarella;是的,我得到一个“内存不足”异常,正如我所说,我不知道为什么,也许你可以试试 SiliconMind 代码并回答这个问题。它已分配,否则无法构建。我编辑我的答案以添加两张照片,请查看它们
    猜你喜欢
    • 2014-09-30
    • 2018-03-26
    • 1970-01-01
    • 2021-10-16
    • 1970-01-01
    • 2010-09-22
    • 2012-11-23
    • 1970-01-01
    • 2014-12-11
    相关资源
    最近更新 更多