web-dev-qa-db-ja.com

OutOfMemoryExceptionのキャッチはどのように機能しますか?

Try/catchブロックを使用してOutOfMemoryExceptionをキャッチできるという事実について少し混乱しています。

次のコードがあるとします:

Console.WriteLine("Starting");

for (int i = 0; i < 10; i++)
{
    try
    {
        OutOfMemory();
    }
    catch (Exception exception)
    {
        Console.WriteLine(exception.ToString());
    } 
}

try
{
    StackOverflow();
}
catch (Exception exception)
{
    Console.WriteLine(exception.ToString());
}

Console.WriteLine("Done");

OutOfMemory + StackOverflowExceptionの作成に使用したメソッド:

public static void OutOfMemory()
{
    List<byte[]> data = new List<byte[]>(1500);

    while (true)
    {
        byte[] buffer = new byte[int.MaxValue / 2];

        for (int i = 0; i < buffer.Length; i++)
        {
            buffer[i] = 255;
        }

        data.Add(buffer);
    }
}

static void StackOverflow()
{
    StackOverflow();
}

OutOfMemoryExceptionを10回出力し、処理できないStackOverflowExceptionが原因で終了します。

RAMグラフは、プログラムの実行中は次のようになります。 graph showing that memory gets allocated and released 10 times

私の質問ですが、なぜOutOfMemoryExceptionをキャッチできるのですか?それをキャッチした後、必要なコードを実行することができます。 RAMグラフで証明されているように、メモリが解放されています。ランタイムは、GCできるオブジェクトと、さらに実行するために必要なオブジェクトをどのようにして知るのですか?

24
GameScripting

GCは、プログラムで使用されている参照を分析し、どこでも使用されていないオブジェクトを破棄できます。

OutOfMemoryExceptionは、メモリが完全になくなったことを意味するのではなく、メモリ割り当てが失敗したことを意味します。一度に大きなメモリ領域を割り当てようとした場合でも、十分な空きメモリが残っている可能性があります。

割り当てに十分な空きメモリがない場合、システムはガベージコレクションを実行してメモリを解放しようとします。それでも割り当てに十分なメモリがない場合は、例外がスローされます。

StackOverflowExceptionは処理できません。これは、スタックがいっぱいであり、ヒープの場合のようにスタックから何かを削除することができないためです。例外を処理するコードを実行し続けるには、より多くのスタックスペースが必要ですが、それ以上はありません。

27
Guffa

32ビットプログラムを実行しているためOutOfMemoryExceptionがスローされる可能性が高く、メモリグラフでシステムのRAMの量を示していないため、64ビットとしてビルドしてみてください MemoryFailPoint =とにかくこれが発生するのを防ぎます。

OutOfMemory()関数の内容をわかりやすく説明することもできます。

追伸StackOverFlowは、処理できない唯一のエラーです。

編集:上記のように、私はそれを論理的であると考えたため、前に言及しませんでした。たとえば、「スペア」よりも多くのメモリを割り当てようとすると、そうすることができず、例外が発生します。 data.Add()で大きな配列を割り当てると、最後の「不正な」追加が発生する前にフォールオーバーするため、まだ空きメモリがあります。

したがって、この時点でdata.Add(buffer);であると想定します。この問題は、400MBのバイト配列を「データ」に追加して2GBのプロセス制限を超えると、配列の構築中に発生します。 4バイトで約10億個のオブジェクトの配列1ピースは約400MBになると予想されます。

追伸.net 4.5までの最大プロセスメモリ割り当ては2GBで、4.5より大きい後は利用可能です。

6
Paul Zahra

これがあなたの質問に答えるかどうかはわかりませんが、クリーンアップするオブジェクトを決定する方法についての(簡略化された)説明は次のとおりです。

ガベージコレクターは、プログラムで実行中のすべてのスレッドを取り、すべての最上位オブジェクトをマークします。つまり、スタックフレームからアクセス可能なすべてのオブジェクト(つまり、現在実行されているポイントでローカル変数が指すすべてのオブジェクト)とすべて静的フィールドが指すオブジェクト。

次に、次のレベルのオブジェクトをマークします。これは、以前にマークされたオブジェクトのすべてのフィールドが指すすべてのオブジェクトを意味します。この手順は、新しいオブジェクトがマークされなくなるまで繰り返されます。

C#は通常のコンテキストではポインターを許可しないため、前の手順が完了すると、マークされていないオブジェクトが後続のコードからアクセスできなくなり、安全にクリーンアップできることが保証されます。

あなたのケースでは、メモリマネージャーに圧力を加えるために割り当てたオブジェクトが参照によって保持されていない場合、GCがそれらをクリーンアップする機会があることを意味します。また、OutOfMemoryExceptionはCLRプログラムのマネージメモリを参照しますが、GCはその「ボックス」の外で少し動作することに注意してください。

OutOfMemoryExceptionをキャッチできるのは、言語デザイナーが許可することにしたためです。これがときどき(ただし通常ではない)実用的な理由は、場合によっては回復可能な状況であるためです。

巨大な配列を割り当てようとすると、OutOfMemoryExceptionが発生する可能性がありますが、その巨大な配列のメモリは実際には割り当てられていないため、他のコードは問題なく実行できます。また、例外によるスタックの巻き戻しにより、他のオブジェクトがガベージコレクションの対象となり、使用可能なメモリの量がさらに増える可能性があります。

0
Matthew Watson

OutOfMemory()メソッドはデータ構造を作成します(List<byte[]>)メソッドのスコープに対してローカルです。実行のスレッドがOutOfMemoryメソッド内にある間、現在のスタックフレームはリストのGCルートと見なされます。スレッドがcatchブロックに到達すると、スタックフレームがポップされ、リストは事実上到達できなくなります。したがって、ガベージコレクターは、リストを安全に収集できると判断します(これは、メモリグラフで確認したとおりです)。

0
afrischke