25

64 ビット WPF テスト アプリを作成しました。アプリを実行し、タスク マネージャーを開いた状態で、システム メモリの使用状況を監視します。2GB を使用していて、6GB の空き容量があります。

私のアプリでは、[追加] ボタンをクリックして、新しい 1GB バイト配列をリストに追加します。システムのメモリ使用量が 1GB 増加しています。[追加] を合計 6 回クリックし、開始時に使用できた 6 GB のメモリをいっぱいにしました。

[削除] ボタンを 6 回クリックして、リストから各アレイを削除します。削除されたバイト配列は、コントロール内の他のオブジェクトから参照されるべきではありません。

削除しても、メモリが減ることはありません。しかし、GC が非決定論的であることなどを理解しているので、それで問題ありません。必要に応じてGCが収集すると思います。

そのため、メモリがいっぱいに見えますが、必要に応じて GC が収集されることを期待して、もう一度追加します。私の PC は、ディスク スラッシング コマに出入りし始めます。GC が収集しなかったのはなぜですか? その時でなければ、いつですか?

健全性チェックとして、GC を強制するボタンがあります。それを押すと、すぐに 6GB が戻ってきます。それは、私の 6 つの配列が参照されておらず、GC が知っていた/望んでいた場合に収集された可能性があることを証明しませんか?

私は GC.Collect() を呼び出すべきではないということをたくさん読みましたが、この状況で GC が収集されない場合、他に何ができますか?

    private ObservableCollection<byte[]> memoryChunks = new ObservableCollection<byte[]>();
    public ObservableCollection<byte[]> MemoryChunks
    {
        get { return this.memoryChunks; }
    }

    private void AddButton_Click(object sender, RoutedEventArgs e)
    {
        // Create a 1 gig chunk of memory and add it to the collection.
        // It should not be garbage collected as long as it's in the collection.

        try
        {
            byte[] chunk = new byte[1024*1024*1024];

            // Looks like I need to populate memory otherwise it doesn't show up in task manager
            for (int i = 0; i < chunk.Length; i++)
            {
                chunk[i] = 100;
            }

            this.memoryChunks.Add(chunk);                
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("Could not create another chunk: {0}{1}", Environment.NewLine, ex.ToString()));
        }
    }

    private void RemoveButton_Click(object sender, RoutedEventArgs e)
    {
        // By removing the chunk from the collection, 
        // I except no object has a reference to it, 
        // so it should be garbage collectable.

        if (memoryChunks.Count > 0)
        {
            memoryChunks.RemoveAt(0);
        }
    }

    private void GCButton_Click(object sender, RoutedEventArgs e)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
4

3 に答える 3

18

健全性チェックとして、GC を強制するボタンがあります。それを押すと、すぐに 6GB が戻ってきます。それは、私の 6 つの配列が参照されておらず、GC が知っていた/望んでいた場合に収集された可能性があることを証明しませんか?

あなたはおそらく尋ねたほうがよいでしょうWhen does the GC automatically collect "garbage" memory?。私の頭の上から:

  • 最も一般的なのは、ジェネレーション 0 がいっぱいであるか、オブジェクトの割り当てが使用可能な空き領域に収まらない場合です。1
  • ある程度一般的に、メモリのチャンクを割り当てると が発生するOutOfMemoryException場合、フル GC がトリガーされ、最初に使用可能なメモリを再利用しようとします。コレクション後に十分な連続メモリが利用できない場合、OOM 例外がスローされます。

ガベージ コレクションを開始すると、GC は収集する必要がある世代 (0、0+1、またはすべて) を決定します。各世代には、GC によって決定されるサイズがあります (アプリケーションの実行時に変更される可能性があります)。ジェネレーション 0 だけが予算を超える場合、ガベージが収集されるのはそのジェネレーションのみです。ジェネレーション 0 を生き残ったオブジェクトが原因でジェネレーション 1 が予算を超えた場合、ジェネレーション 1 も収集され、その生き残ったオブジェクトはジェネレーション 2 (Microsoft の実装では最高のジェネレーション) に昇格されます。ジェネレーション 2 の予算も超えた場合、ガベージは収集されますが、オブジェクトが存在しないため、より高いジェネレーションに昇格することはできません。

したがって、ここに重要な情報があります。GC が開始される最も一般的な方法では、ジェネレーション 0 とジェネレーション 1 の両方がいっぱいの場合にのみジェネレーション 2 が収集されます。また、85,000 バイトを超えるオブジェクトは、ジェネレーション 0、1、および 2 の通常の GC ヒープに格納されないことを知っておく必要があります。実際には、ラージ オブジェクト ヒープ (LOH) と呼ばれるものに格納されます。LOH のメモリは、FULL コレクション中 (つまり、ジェネレーション 2 が収集されるとき) にのみ解放されます。ジェネレーション 0 または 1 のみが収集されている場合は never 。

GC が収集しなかったのはなぜですか? その時でなければ、いつですか?

これで、GC が自動的に行われなかった理由が明らかになったはずです。LOH でのみオブジェクトを作成しています(int使用した方法で型がスタックに割り当てられ、収集する必要がないことに注意してください)。ジェネレーション 0 がいっぱいになることはないため、GC は発生しません。1

また、64 ビット モードで実行しているため、上記の他のケース (アプリケーション全体で特定のオブジェクトを割り当てるのに十分なメモリがない場合にコレクションが発生する) に該当する可能性は低いことを意味します。64 ビット アプリケーションには 8 TB の仮想アドレス空間の制限があるため、このケースに到達するまでにはしばらく時間がかかります。それが起こる前に、物理メモリとページ ファイル領域が不足する可能性が高くなります。

GC が発生していないため、Windows はページ ファイル内の使用可能な領域からアプリケーションにメモリを割り当て始めます。

私は GC.Collect() を呼び出すべきではないということをたくさん読みましたが、この状況で GC が収集されない場合、他に何ができますか?

GC.Collect()この種のコードを記述する必要がある場合は呼び出します。いっそのこと、テスト以外でこの種のコードを書かないでください。

結論として、私は CLR の自動ガベージ コレクションのトピックを正しく説明していません。これについては、msdn のブログ投稿 (実際には非常に興味深いものです) を読むことをお勧めします。または、既に述べたように、Jeffery Richter の優れた本 CLR Via C# の第 21 章を読むことをお勧めします。


1 GC の .NET 実装が世代別ガベージ コレクタであることを理解していることを前提としています。簡単に言えば、新しく作成されたオブジェクトがより低い番号の世代、つまり世代 0 にあることを意味します。ガベージ コレクションが実行され、特定の世代にあるオブジェクトに GC ルート (「ガベージ」ではない) があることが判明すると、次世代アップに昇格します。GC には時間がかかり、パフォーマンスが低下する可能性があるため、これはパフォーマンスの向上です。一般に、上位世代のオブジェクトは寿命が長く、アプリケーション内でより長く存在するため、下位世代ほどその世代のガベージをチェックする必要がないという考え方です。このウィキペディアの記事で詳細を読むことができます. エフェメラルGCとも呼ばれます。

2信じられないかもしれませんが、チャンクの 1 つを削除した後、一連のランダムな文字列またはオブジェクトを作成する関数を用意してください (このテストでは、プリミティブの配列を使用しないことをお勧めします)。一定量のスペースに達すると、フル GC が発生し、LOH で割り当てたメモリが解放されます。

于 2012-04-04T19:12:07.170 に答える
5

これは LOH (ラージ オブジェクト ヒープ) で行われます。これらは、第 2 世代の収集が実行されたときにのみクリアされます。Hans がコメントで述べたように、これが実際のコードである場合は、より多くの RAM が必要になります。

くすくす笑いの場合は、 を呼び出しGC.GetGeneration(chunk)て、それが返されることを確認できます2

Jeffrey Richter による C# による CLR、第 3 版 (588 ページ) を参照してください。

于 2012-04-04T17:59:04.023 に答える
2

膨大な量のデータを割り当てて解放する必要がある実際のコードの場合、巨大なオブジェクトの処理が完了したら、手動で GC ( GC.Collect best practice question ) を呼び出すことを検討してください。

より小さな (80K 未満) チャンクで割り当てを行うことで、オブジェクトを LOH から通常のヒープにシフトすることもできます。

于 2012-04-04T18:02:02.507 に答える