6

私はOutOfMemoryException、いくつかのバイト配列を作成して処理するメソッド内で常にヒットしています。コードは次のようになります。

  1. 一部のデータ (約 60MB) を取得するために MemoryStream を作成します。
  2. バイト配列を作成(MemoryStreamと同サイズ、約60MB)
  3. メモリ ストリームからのバイトで配列を埋める
  4. メモリストリームを閉じる
  5. バイト配列からデータを処理する
  6. 退会方法

このメソッドが 20 ~ 30 回呼び出されるとOutOfMemoryException、バイト配列がどこに割り当てられているかがわかります。しかし、システムメモリの問題ではないと思います。アプリケーションのメモリ使用量は約 500MB (プライベート ワーキング セット) で、テスト マシンは 4GB の RAM を備えた 64 ビットです。

バイト配列で使用されているメモリ、または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 のアイテムは収集されますが、圧縮されません。

解決策は次のとおりです。

  • 最初に、何かが収集されるのを妨げていないことを確認してください。プロファイラーを使用します。
  • バッファを再利用してみてください。
  • 割り当てを一連の固定数に丸めます。

最後の 2 つはどちらも特大の配列で作業する必要があり、多少の作業が必要になる場合があります。

于 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 に答える
-4

まず、TSQL を使用して大きな FILESTREAM データを読み書きすることはお勧めしません。推奨されるアプローチは、SQL サーバーによって提供される Win32/DOTNET API を使用することです。上記のコードは、SqlFileStream() .net クラスを使用して FILESTREAM データにアクセスする方法を示しています。また、データを小さなチャンクで送信する方法も示します。

合計メモリが十分であれば、小さな配列の束を作成し、それらを単一の IList またはその他のインデックス付きインターフェイスにラップすることで、LOH の断片化に起因するメモリ不足の例外を防ぐことができます。

于 2012-03-27T13:04:53.283 に答える