OutOfMemoryException при создании нескольких байтовых массивов - PullRequest
6 голосов
/ 27 марта 2012

Я постоянно нажимаю OutOfMemoryException внутри метода, который создает и обрабатывает некоторые байтовые массивы.Код выглядит следующим образом:

  1. Создание MemoryStream для получения некоторых данных (около 60 МБ).
  2. Создание байтового массива (такого же размера, как MemoryStream, около 60 МБ)
  3. Заполнить массив байтами из потока памяти
  4. Закрыть MemoryStream
  5. Обрабатывать данные из байтового массива
  6. Оставить метод

Когда этот метод вызывается как20-30 раз я получаю OutOfMemoryException прямо там, где расположен массив байтов.Но я не думаю, что это проблема системной памяти.Использование памяти приложением составляет около 500 МБ (частный рабочий набор), а тестовый компьютер является 64-разрядным с 4 ГБ ОЗУ.

Возможно ли, что память, используемая байтовым массивом или MemoryStream, не освобождается после завершения метода?Но тогда не похоже, что эта память выделена для процесса, поскольку частный рабочий набор составляет всего 500 МБ или около того.

Что может вызвать OutOfMemoryException при создании большого байтового массива (60 МБ) помимо недостатка физической памяти?

[отредактировано для добавления образца кода] Источник взят из 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;
  }
}

Ответы [ 6 ]

6 голосов
/ 27 марта 2012

Я надеюсь, что вы используете MemoryStream.GetBuffer() и не копируете в новый массив.

Ваша главная проблема не в прямой нехватке памяти, а в фрагментации LOH .Это может быть сложной проблемой, основная проблема заключается в выделении больших буферов разного размера.Элементы на LOH собираются, но не уплотняются.

Решения могут быть следующими:

  • Сначала убедитесь, что вы ничего не блокируете от сбора.использовать профилировщик.
  • попробуйте повторно использовать ваш буфер (ы).
  • округление до до набора фиксированных чисел.

Последние два варианта требуют работы с массивами увеличенного размера, может потребоваться некоторая работа.

3 голосов
/ 27 марта 2012

Утилизация была предложена, но только для MemoryStream, это ничего вам не даст.Кроме того, GC.Collect было предложено.Это может не помочь, так как кажется, что ваша память существенно не растет.Будьте осторожны, вызов GC.Collect может быть дорогостоящей операцией.

Фрагментация

Похоже, вы столкнулись с печально известной проблемой фрагментации кучи больших объектов.Это может быть вызвано частым выделением и освобождением фрагментов 60 МБ памяти.Если LOH становится фрагментированным, он остается фрагментированным.Это серьезная проблема с долго работающими приложениями .NET и причина, по которой ASP.NET часто настраивается на перезапуск с интервалами.

Предотвратить OutOfMemoryException

См. Выше статью CodeProject о том, как это сделать.Трюк в том, чтобы использовать MemoryFailPoint и поймать InsufficientMemoryException.Таким образом, вы можете изящно ухудшиться, и ваше приложение не станет нестабильным.

Возможное общее решение

Убедитесь, что ваши крупные объекты живут как можно дольше.Повторно используйте буфер.Выделите его один раз с достаточным размером и обнулите буфер, когда вам это понадобится снова.Таким образом, вы не столкнетесь с другими проблемами с памятью.Когда размер ваших объектов не превышает 85 КБ, они обычно не попадают в LOH и не загромождают.

64-битные машины не должны иметь этой проблемы

РЕДАКТИРОВАТЬ: Согласно этот пост (вкладка обходного пути) и этот пост (см. открытый комментарий), эта проблема не должна появляться на 64-битных машинах.Поскольку вы говорите, что запускаете свой код на 64-битных машинах, возможно, вы скомпилировали его с конфигурацией x86?

0 голосов
/ 19 февраля 2018

У меня та же проблема в коде, как показано ниже:

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, как и я!Exception Debug info

0 голосов
/ 27 марта 2012

Прежде всего, не рекомендуется читать / записывать большие данные FILESTREAM через TSQL.Рекомендуемый подход заключается в использовании API-интерфейсов Win32 / DOTNET, предоставляемых сервером SQL.Код, который я разместил выше, показывает, как получить доступ к данным FILESTREAM с помощью класса SqlFileStream () .net.Также показано, как отправлять данные небольшими порциями.

При условии, что вашей общей памяти достаточно, вы можете предотвратить исключения нехватки памяти, возникающие из-за фрагментации LOH, создав кучу меньших массивов и поместив их водин IList или какой-либо другой индексированный интерфейс.

0 голосов
/ 27 марта 2012

Попробуйте заключить его в блок using(MemoryStream x = ...) { }, который будет располагать объект для вас.

Хотя Close должен Dispose объект, в соответствии с рекомендациями .NET, возможно, он отличается вMemoryStream.

0 голосов
/ 27 марта 2012

Память кучи выдает это исключение, попробуйте вызвать GC.Collect () в конце, чтобы освободить ресурсы.Вы также можете MemoryProfiler , чтобы выяснить использование памяти кучи, она поставляется с 14-дневной пробной версией

...