15

私は次のコードを持っています:

const int bufferSize = 1024 * 1024;
var buffer = new byte[bufferSize];
for (int i = 0; i < 10; i++)
{
    const int writesCount = 400;
    using (var stream = new MemoryStream(writesCount * bufferSize))
    {
        for (int j = 0; j < writesCount; j++)
        {
            stream.Write(buffer, 0, buffer.Length);
        }
        stream.Close();
    }
}

私は32ビットマシンで実行しています。

最初の反復は問題なく終了し、次の反復System.OutOfMemoryExceptionで、の行に例外が発生しnewますMemoryStream

ステートメントにもかかわらず、以前のMemoryStreamメモリが回収されないのはなぜですか?using使用されているメモリを強制的に解放するにはどうすればよいMemoryStreamですか?

4

4 に答える 4

14

問題は、ガベージコレクターがその役割を果たしていないことではないと思います。GCがメモリ不足の場合は、実行して、割り当てたばかりの400MBを再利用する必要があります。

これは、GCが連続する400MBのブロックを見つけられないことが原因である可能性が高くなります。

むしろ、「メモリ不足」エラーが発生するのは、プロセスが、要求されたマッピングを実行するのに十分な大きさの連続する未使用ページのセクションを仮想アドレス空間で見つけることができないためです。

EricLippertのブログエントリ「OutOfMemory」は物理メモリを指していませんを読む必要があります

以下の両方を行う方がはるかに良いでしょう。

  1. 割り当てたメモリブロックを再利用する(まったく同じサイズの別のメモリブロックを作成する理由)
  2. はるかに小さいチャンク(85KB未満)の割り当て

Dotnet 4.5より前は、Dotnetは2つのヒープ、Small Object Heap(SOH)Large Object Heap(LOH)を構築していました。BrandonBrayによる.NET4.5でのラージオブジェクトハープの改善を参照してください。あなたMemoryStreamはLOHで割り当てられており、プロセスの期間中は圧縮(最適化)されていないため、この大量のメモリを割り当てるための複数の呼び出しによって、OutOfMemoryException

CLRは、割り当て用に2つの異なるヒープ、スモールオブジェクトヒープ(SOH)とラージオブジェクトヒープ(LOH)を管理します。85,000バイト以上の割り当ては、LOHで行われます。大きなオブジェクトをコピーするとパフォーマンスが低下するため、SOHとは異なりLOHは圧縮されません。もう1つの明確な特徴は、LOHが第2世代の収集中にのみ収集されることです。同時に、これらには、大きなオブジェクトの割り当てがまれであるという前提が組み込まれています。

于 2012-08-17T08:48:02.497 に答える
1

参照されていないオブジェクトをクリーンアップする必要があることが確実な場合は、ガベージコレクションを強制してみてください。

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

もう1つの方法はStream、外部ストレージでを使用することFileStreamです。たとえば、。

ただし、一般的なケースでは、十分に小さい1つのバッファー(配列、1回割り当て)を使用して、読み取り/書き込み呼び出しに使用することをお勧めします。.NETに多数の大きなオブジェクトを含めることは避けてください( CLR Inside Out:Large Object Heap Uncoveredを参照)。

アップデート

writesCountが定数であると仮定して、 1つのバッファを割り当てて再利用してみませんか?

const int bufferSize = 1024 * 1024;
const int writesCount = 400;

byte[] streamBuffer = new byte[writesCount * bufferSize];
byte[] buffer = new byte[bufferSize];
for (int i = 0; i < 10; i++)
{
    using (var stream = new MemoryStream(streamBuffer))
    {
        for (int j = 0; j < writesCount; j++)
        {
            stream.Write(buffer, 0, buffer.Length);
        }
    }
}
于 2012-08-17T08:39:36.927 に答える
1

システムが処理できるよりも多くを割り当てているようです。あなたのコードは私のマシンで正常に動作しますが、次のように変更すると:

const int bufferSize = 1024 * 1024 * 2;

あなたと同じエラーが発生します。

しかし、ターゲットプロセッサをx64に変更すると、コードが実行されます。これは、より多くのメモリをアドレス指定できるため、論理的に見えます。

この記事の詳細な説明:http://www.guylangston.net/blog/Article/MaxMemory そしてこの質問に関するいくつかの情報:.NETプロセスが割り当てることができる最大メモリ

于 2012-08-17T08:45:51.267 に答える
1

まず第一に、メモリが解放されることを保証するものではありません(管理されていないリソースがないため、何も解放しないDispose()場合は、GCコレクションのオブジェクトをマークしません)。によって使用されるメモリを解放する唯一の信頼できる方法は、それへのすべての参照を失い、ガベージコレクションが発生するのを待つことです(ガベージコレクタがすでに試行されていますが、十分なメモリを解放できませんでした)。また、このような大きなオブジェクト(85000バイトを超えるもの)を割り当てると、いくつかの結果が生じます。これらのオブジェクトは、断片化される可能性がある(圧縮できない)大きなオブジェクトヒープ(LOH)になります。.NETオブジェクトは連続したバイトシーケンスを占有する必要があるため、十分なメモリがある状況につながる可能性がありますが、大きなオブジェクトを格納する余地はありません。この場合、ガベージコレクターは役に立ちません。MemoryStreamMemoryStreamMemoryStreamOutOfMemoryException

ここでの主な問題は、streamオブジェクトへの参照がスタックに保持され、オブジェクトのガベージコレクションが妨げstreamられていることです(ガベージコレクションを強制しても、GCはオブジェクトがまだ生きていると見なすため、これをチェックしてオブジェクトを作成できますWeakRefrence) )。このサンプルをリファクタリングすると、修正できます。

    static void Main(string[] args)
    {
        const int bufferSize = 1024 * 1024 * 2;
        var buffer = new byte[bufferSize];
        for(int i = 0; i < 10; i++)
        {
            const int writesCount = 400;
            Write(buffer, writesCount, bufferSize);
        }
    }

    static void Write(byte[] buffer, int writesCount, int bufferSize)
    {
        using(var stream = new MemoryStream(writesCount * bufferSize))
        {
            for(int j = 0; j < writesCount; j++)
            {
                stream.Write(buffer, 0, buffer.Length);
            }
        }
    }

オブジェクトをガベージコレクションできないことを証明するサンプルを次に示します。

    static void Main(string[] args)
    {
        const int bufferSize = 1024 * 1024 * 2;
        var buffer = new byte[bufferSize];
        WeakReference wref = null;
        for(int i = 0; i < 10; i++)
        {
            if(wref != null)
            {
                // force garbage collection
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                // check if object is still alive
                Console.WriteLine(wref.IsAlive); // true
            }
            const int writesCount = 400;
            using(var stream = new MemoryStream(writesCount * bufferSize))
            {
                for(int j = 0; j < writesCount; j++)
                {
                    stream.Write(buffer, 0, buffer.Length);
                }
                // weak reference won't prevent garbage collection
                wref = new WeakReference(stream);
            }
        }
    }
于 2012-08-17T08:58:52.587 に答える