26

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

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

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();
}

10回出力し、処理できないOutOfMemoryExceptionために終了します。StackOverflowException

プログラムの実行中、RAM グラフは次のようになります。 メモリの割り当てと解放が 10 回行われることを示すグラフ

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

4

5 に答える 5

33

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

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

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

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

于 2012-12-12T08:51:06.200 に答える
6

OutOfMemoryException は、32 ビット プログラムを実行しているためにスローされる可能性が非常に高く、メモリ グラフではシステムの RAM の量が示されていないため、64 ビットとしてビルドしてみてください

OutOfMemory() 関数の内容をお知らせいただければ、より明確に把握できます。

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

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

したがって、この時点で data.Add(buffer); であると想定します。この問題は、配列の構築中に、400MB バイトの配列を「データ」に追加して 2GB のプロセス制限をトリップするときに発生します。

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

于 2012-12-12T08:48:34.663 に答える
0

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

于 2012-12-12T09:06:48.573 に答える
0

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

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

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

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

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

于 2012-12-12T08:49:47.783 に答える
0

OutOfMemoryException をキャッチできるのは、言語設計者が許容することにしたためです。これが時々 (通常ではありませんが) 実用的である理由は、場合によっては回復可能な状況であるためです。

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

于 2012-12-12T08:50:31.887 に答える