6

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

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

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

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

OutOfMemoryException除了物理内存不足之外,创建大字节数组(60MB)时可能会导致什么?

[编辑添加代码示例] 源来自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;
  }
}
4

6 回答 6

6

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

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

解决方案可能是:

  • 首先确保你没有阻止任何东西被收集。使用探查器。
  • 尝试重用您的缓冲区。
  • 将分配四舍五入一组固定数字。

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

于 2012-03-27T13:02:14.943 回答
3

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

碎片化

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

防止 OutOfMemoryException

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

可能的通用解决方案

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

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

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

于 2012-03-27T13:16:47.970 回答
0

您的堆内存正在抛出此异常,请尝试在最后调用 GC.Collect() 以释放资源。您还可以通过MemoryProfiler了解堆内存使用情况,它提供 14 天试用期

于 2012-03-27T13:02:09.377 回答
-1

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

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

于 2012-03-27T13:04:40.557 回答
-3

我在下面的代码中有同样的问题:

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();可以解决问题。
我认为问题的出现是因为您在块MemoryStream memory = new MemoryStream();之外定义if并在块中关闭它if,就像我一样! 例外 调试信息

于 2018-02-19T10:13:02.180 回答
-4

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

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

于 2012-03-27T13:04:53.283 回答