GC.Collectの呼び出しはいつ受け入れられますか?


166

一般的なアドバイスはGC.Collect、コードから呼び出すべきではないということですが、このルールの例外は何ですか?

ガベージコレクションを強制することが理にかなっている非常に特殊なケースをいくつかしか考えられません。

思い浮かぶ1つの例は、一定の間隔で目を覚まし、いくつかのタスクを実行し、その後長時間スリープするサービスです。この場合、まもなくアイドルになるプロセスが必要以上のメモリを保持しないように収集を強制することをお勧めします。

他に電話しても問題ない場合はありますGC.Collectか?


回答:


153

重要なオブジェクトのセット、特に世代1と2にあると思われるオブジェクトがガベージコレクションの対象になり、パフォーマンスヒットが小さいという点で適切な時期になると確信できる正当な理由がある場合。

これの良い例は、大きなフォームを閉じたばかりの場合です。すべてのUIコントロールをガベージコレクションできるようになりました。フォームが閉じられたときの非常に短い一時停止は、おそらくユーザーには気付かないでしょう。

アップデート2.7.2018

.NET 4.5以降- GCLatencyMode.LowLatencyおよびがありGCLatencyMode.SustainedLowLatencyます。これらのモードのいずれかに入るときと出るときは、を使用してフルGCを強制することをお勧めしますGC.Collect(2, GCCollectionMode.Forced)

.NET 4.6以降- GC.TryStartNoGCRegionメソッドがあります(読み取り専用の値を設定するために使用GCLatencyMode.NoGCRegion)。これ自体、十分なメモリを解放するためにフルブロッキングガベージコレクションを実行できますが、GCを一定期間禁止しているため、前後にフルGCを実行することもお勧めします。

出典:マイクロソフトエンジニアのベンワトソンズ:高性能.NETコードの作成、第2版。2018。

見る:


8
MSソースコードによると、GC.Collect(2)を呼び出すと、850msごとに問題ありません。信じない?次に、PresentationCore.dll、MS.Internal.MemoryPressure.ProcessAdd()を確認します。私は現在、GC.Collect(2)の呼び出しに850ミリ秒以上かかる画像処理アプリ(小さな画像、実際のメモリ負荷がないもの)を持っているため、アプリ全体がこれによりフリーズします(GCで99.7%の時間を費やしているアプリ)。
springy76

36
@ springy76:Microsoftによって1か所で行われているからといって、 Microsoft からアドバイスを提供ている人にとって良いことだとは限りません...
Jon Skeet

4
その例は好きではありません。フォームを閉じた後にそれを行う際の問題は何ですか?XBoxまたはWindowsPhoneでゲームレベルを読み込んだ後の例がわかります。それらのプラットフォームでは、GCは1MBまたはそのようなsthを割り当てた後に実行されます。したがって、(スプラッシュ画面を表示しながら)レベルのロード中にできる限り多くを割り当ててから、GC.Collectを実行して、ゲーム中にコレクションを回避することをお勧めします。
Piotr Perak 2011

8
@Peri:フォームを閉じた後に行うポイントは、ガベージコレクションの対象となるオブジェクト(コントロール、表示しているデータ)の束を作成したGC.Collectことです。つまり、呼び出しによって、ガベージコレクターに基本的に知っていることを伝えます。変更のためのそれよりも良い。この例が気に入らないのはなぜですか。
Jon Skeet

6
@SHCJ:GC.Collect()GCが完全なコレクションを実行することを要求します。あなたはあなただけのガベージコレクションの対象以前に長命のオブジェクトの多くを作ったことがわかっている場合、ユーザーが後でより今にくいわずかな一時停止を通知にあると信じて、それは今より良いです考えるのは完全に合理的なようです後で行うよりも、コレクションを促す時間。
Jon Skeet

50

私はGC.Collect、大まかなパフォーマンス/プロファイラーテストリグを作成するときにのみ使用します。つまり、テストするコードのブロックが2つ(またはそれ以上)あります。

GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestA(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
TestB(); // may allocate lots of transient objects
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
...

ようにTestA()し、TestB()可能な限り同様の状態などと実行-つまりは、TestB()という理由だけで打たれないTestA非常に近い転換点にそれを残しました。

古典的な例はMain、ループされた文字列連結との違いを示す単純なコンソールexe(たとえば、ここに投稿するのに十分なメソッド)StringBuilderです。

正確なものが必要な場合、これは2つの完全に独立したテストになります。ただし、テスト中にGCを最小化(または正規化)して動作の大まかな感じを得たい場合は、これで十分です。

量産コード中?まだ使っていません;-p


1
この場合も、おそらく "WaitForPendingFinalizers"(またはそれが何であれ)も追加します;-p
Marc Gravell

29

ベストプラクティスは、ほとんどの場合、ガベージコレクションを強制しないことです。 (私が取り組んだすべてのシステムは、ガベージコレクションを強制し、解決するとガベージコレクションを強制する必要がなくなり、システムを大幅に高速化するという問題に下線を引いていました。)

ありますいくつかのケースあなたはその後、ガベージコレクタが行うメモリ使用量の詳細を知っているが。これは、マルチユーザーアプリケーション、または一度に複数の要求に応答するサービスでは当てはまりません。

ただし、一部のバッチタイプの処理では、GCよりもよく知っています。たとえば、そのアプリケーションを検討してください。

  • コマンドラインでファイル名のリストが与えられます
  • 単一のファイルを処理し、結果を結果ファイルに書き出します。
  • ファイルの処理中に、ファイルの処理が完了するまで収集できない多数の相互リンクされたオブジェクトを作成します(例:解析ツリー)
  • 処理したファイル間の一致状態を保持しません

あなたはありますが、それぞれのファイルを処理した後で、完全なガベージコレクションを強制する必要があることをテストする(気をつけた後)のケースを作ることができます。

もう1つのケースは、数分ごとに起動していくつかのアイテムを処理し、スリープ状態の間は状態を維持しないサービスです。次に、スリープ状態になる直前に完全なコレクションを強制することは価値があります。

コレクションの強制を検討するのは、最近多くのオブジェクトが作成され、現在参照されているオブジェクトがほとんどないことを知っているときだけです。

GCに自分自身を強制する必要なしに、このタイプのことについてのヒントを与えることができる場合は、ガベージコレクションAPIを使用したいと思います。

Rico MarianiのパフォーマンスTidbits」も参照してください。



11

大規模な24/7または24/6システム(メッセージ、RPC要求に反応するシステム、またはデータベースやプロセスを継続的にポーリングするシステム)では、メモリリークを特定する方法があると便利です。このため、アプリケーションにメカニズムを追加して一時的に処理を一時停止してから、完全なガベージコレクションを実行する傾向があります。これにより、システムは静止状態になり、残りのメモリは正当な長命のメモリ(キャッシュ、構成など)であるか、または「リーク」されます(ルート化されることが期待されていないか、望まれていないが、実際にはオブジェクトです)。

このメカニズムを使用すると、アクティブな処理によるノイズでレポートが曇ることがないため、メモリ使用量のプロファイルが非常に簡単になります。

すべてのゴミを確実に取得するには、2つのコレクションを実行する必要があります。

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

最初のコレクションにより、ファイナライザを含むオブジェクトがファイナライズされます(ただし、実際にはこれらのオブジェクトのガベージコレクションは行われません)。2番目のGCは、これらのファイナライズされたオブジェクトをガベージコレクションします。


私はいくつかの場所で2パスのコレクションを見てきましたが、MSCのドキュメントでGC.WaitForPendingFinalizersについての文章を読んだ後、次のように述べています。以下のワーカーループはファイナライザーと同時に実行される可能性があります。この呼び出しでは、ワーカーループはすべてのファイナライザーが呼び出された後でのみ実行されます。私はただの偏執狂です。2つのパスを実行するための確実なソースを知っていますか?
jerhewet

1
@jerhewet:2つのコレクションが必要な理由を理解するための鍵は、ファイナライザでオブジェクトに何が起こるかを理解することです。残念ながら私はあなたが求めているものを正確には知りませんが、この記事この質問をSOで読んでください
ポールルアン

10

ガベージコレクターが認識しないアプリの性質について何か知っている場合は、GC.Collect()を呼び出すことができます。著者として、これは非常に可能性が高いと考えるのは魅力的です。しかし、真実はGCがかなりよく記述され、テストされたエキスパートシステムに相当することであり、GCが理解していない低レベルのコードパスについて何かを知っていることはまれです。

追加の情報が含まれる可能性がある場所として考えられる最も良い例は、アイドル期間と非常にビジーな期間を切り替えるアプリです。繁忙期に可能な限り最高のパフォーマンスを望んでいるため、アイドル時間を使用してクリーンアップを行います。

ただし、ほとんどの場合、GCはとにかくこれを行うのに十分スマートです。


8

メモリの断片化ソリューションとして。大量のデータをメモリストリームに書き込む(ネットワークストリームから読み取る)ときに、メモリ不足の例外が発生していました。データは8Kチャンクで書き込まれました。128Mに達した後、使用可能なメモリはたくさんあっても例外がありました(ただし、断片化されていました)。GC.Collect()を呼び出すと、問題が解決しました。修正後は1G以上に対応できました。


7

Rico Marianiによるこの記事をご覧ください。GC.Collectを呼び出すとき、彼は2つのルールを与えます(ルール1は「しないでください」)。

GC.Collect()を呼び出すタイミング


3
すでにそこにいた。私がやるべきでないことをするための言い訳を探すつもりはありませんが、それが受け入れられる特定のケースがあるかどうか知りたいのですが。
Brian Rasmussen、

5

GC.Collect()を呼び出す必要がほとんどある1つの例は、Interopを使用してMicrosoft Officeを自動化する場合です。OfficeのCOMオブジェクトは自動的に解放されたくないため、Office製品のインスタンスが大量のメモリを消費する可能性があります。これが問題なのか、設計上の問題なのかはわかりません。このトピックについてはインターネット上にたくさんの投稿があるので、あまり詳しく説明しません。

Interopを使用してプログラミングする場合、通常はMarshal.ReleseComObject()を使用して、すべての COMオブジェクトを手動で解放する必要があります。さらに、ガベージコレクションを手動で呼び出すと、少し「クリーンアップ」するのに役立ちます。Interopオブジェクトを使い終わったときに次のコードを呼び出すと、かなり役立つようです。

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

私の個人的な経験では、ReleaseComObjectと手動でガベージコレクションを呼び出すことを組み合わせて使用すると、Office製品、特にExcelのメモリ使用量が大幅に削減されます。


ええ、私は.netがcomオブジェクトを介して機能するExcelにアクセスすることでそれに遭遇しました。GC操作が制限されているため、DEBUGモードではうまく機能しないことに注意してください。RELEASEモードでのみ意図したとおりに機能します。関連リンク:stackoverflow.com/questions/17130382/...
Blechdose

5

私は配列とリストでいくつかのパフォーマンステストを行っていました:

private static int count = 100000000;
private static List<int> GetSomeNumbers_List_int()
{
    var lstNumbers = new List<int>();
    for(var i = 1; i <= count; i++)
    {
        lstNumbers.Add(i);
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Array()
{
    var lstNumbers = new int[count];
    for (var i = 1; i <= count; i++)
    {
        lstNumbers[i-1] = i + 1;
    }
    return lstNumbers;
}
private static int[] GetSomeNumbers_Enumerable_Range()
{
    return  Enumerable.Range(1, count).ToArray();
}

static void performance_100_Million()
{
    var sw = new Stopwatch();

    sw.Start();
    var numbers1 = GetSomeNumbers_List_int();
    sw.Stop();
    //numbers1 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
    var numbers2 = GetSomeNumbers_Array();
    sw.Stop();
    //numbers2 = null;
    //GC.Collect();
    Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds));

    sw.Reset();
    sw.Start();
//getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method
    var numbers3 = GetSomeNumbers_Enumerable_Range();
    sw.Stop();
    //numbers3 = null;
    //GC.Collect();

    Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds));
}

OutOfMemoryExceptionGetSomeNumbers_Enumerable_Rangeメソッドを取得した唯一の回避策は、次の方法でメモリの割り当てを解除することです。

numbers = null;
GC.Collect();

なぜ反対投票ですか?私の答えは、GCを呼び出すタイミングを示す例です。より良い提案はありますか?
ダニエルB

4

あなたの例では、GC.Collectの呼び出しが問題ではなく、デザインの問題があると思います。

一定の間隔(設定された時間)でウェイクアップする場合は、プログラムを1回実行するように作成し(タスクを1回実行する)、終了します。次に、プログラムをスケジュールされた間隔で実行するスケジュールされたタスクとして設定します。

この方法では、GC.Collectの呼び出しについて心配する必要はありません(これを行う必要はほとんどありません)。

そうは言っても、Rico Marianiはこの件に関して素晴らしいブログ記事を公開しています。

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx


3

GC.Collect()を呼び出すのに便利な場所の1つは、メモリリークが発生していないことを確認する場合の単体テストです(たとえば、WeakReferencesまたはConditionalWeakTable、動的に生成されたコードなどで何かを行っている場合)。

たとえば、次のようなテストがあります。

WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);

WeakReferencesを使用すること自体が問題であると主張できますが、そのような動作に依存するシステムを作成している場合は、GC.Collect()を呼び出すことがそのようなコードを検証するための良い方法であるようです。




2
using(var stream = new MemoryStream())
{
   bitmap.Save(stream, ImageFormat.Png);
   techObject.Last().Image = Image.FromStream(stream);
   bitmap.Dispose();

   // Without this code, I had an OutOfMemory exception.
   GC.Collect();
   GC.WaitForPendingFinalizers();
   //
}

2

申し訳ありませんが、より安全な状況がいくつかあります。

これが1つの状況です。

IL書き換えを使用して、C#でアンマネージ DLL を作成することができます(これが必要な状況があるため)。

たとえば、DLLがクラスレベルでバイトの配列を作成するとします。エクスポートされた関数の多くがバイト配列にアクセスする必要があるためです。DLLがアンロードされるとどうなりますか?その時点でガベージコレクターは自動的に呼び出されますか?わかりませんが、アンマネージ DLLであるため、GCが呼び出されない可能性があります。それが呼ばれなければ、それは大きな問題になるでしょう。DLLがアンロードされると、ガベージコレクターも削除されます。つまり、可能なガベージを収集する責任を負うのはだれですか。C#のガベージコレクターを使用することをお勧めします。クラスレベル変数がnullに設定され、ガベージコレクターが呼び出されるクリーンアップ関数(DLLクライアントで使用可能)があります。

転ばぬ先の杖。


2

私はまだこれについてかなり確信が持てません。私は7年間、アプリケーションサーバーに取り組んでいます。より大きなインストールでは、24 GBのRAMを使用します。その非常にマルチスレッド化された、GC.Collect()のすべての呼び出しは、本当にひどいパフォーマンスの問題に遭遇しました。

多くのサードパーティコンポーネントは、GC.Collect()を使用して、現時点でこれを行うのが賢明だと考えました。そのため、Excelレポートの単純な束が、1分間に数回すべてのスレッドのアプリケーションサーバーをブロックしました。

GC.Collect()呼び出しを削除するために、すべてのサードパーティコンポーネントをリファクタリングする必要があり、これを実行した後はすべて正常に動作しました。

しかし、Win32でもサーバーを実行しています。OutOfMemoryExceptionを取得した後、GC.Collect()を頻繁に使用し始めました。

しかし、私もこれにかなり確信が持てません。32ビットでOOMを取得し、GC.Collect()を呼び出さずに同じ操作を再度実行しようとすると、うまく機能したためです。

私が不思議に思うのは、OOM例外自体です... .Net Frameworkを作成していて、メモリブロックを割り当てることができない場合、GC.Collect()を使用してメモリをデフラグ(??)し、再試行します。 、まだ空きメモリブロックが見つからない場合は、OOM例外をスローします。

または、GC.Collectのパフォーマンス問題の欠点のため、少なくともこの動作を構成可能なオプションとして設定します。

今私は私の問題を「解決する」ためにこのようなコードをたくさん持っています:

public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2)
{

    int oomCounter = 0;
    int maxOOMRetries = 10;
    do
    {
        try
        {
            return func(a1, a2);
        }
        catch (OutOfMemoryException)
        {
            oomCounter++;
            if (maxOOMRetries > 10)
            {
                throw;
            }
            else
            {
                Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString());
                System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10));
                GC.Collect();
            }
        }
    } while (oomCounter < maxOOMRetries);

    // never gets hitted.
    return default(TResult);
}

(ORMキャッシュサービスを実行しているため、Thread.Sleep()の動作は実際にはアプリ固有の動作であり、RAMが事前定義された値を超えた場合、サービスはキャッシュされたすべてのオブジェクトを解放するのにしばらく時間がかかります。初回は数秒、OOMが発生するたびに待機時間が長くなります。)


コンポーネントはを呼び出すべきではありませんGC.Collect。これはアプリケーション全体に影響を与えるので、(もしあれば)アプリケーションだけがそうすべきです。
CodesInChaos 2013年

If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(),-彼らはすでにこれを行っていると思います-内部GCトリガーの1つがメモリ割り当ての特定の失敗であるという兆候を見ました。
G.ストイネフ2013

2

GC.Collect()は非常に高価であるため、使用しないようにしてください。次に例を示します。

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet.Remove(RecordSet[0]);

        }
        GC.Collect(); // AVOID
    }

テスト結果:CPU使用率12%

これに変更すると:

        public void ClearFrame(ulong timeStamp)
    {
        if (RecordSet.Count <= 0) return;
        if (Limit == false)
        {
            var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000;
            if (seconds <= _preFramesTime) return;
            Limit = true;
            do
            {
                RecordSet[0].Dispose(); //  Bitmap destroyed!
                RecordSet.Remove(RecordSet[0]);
            } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime);
        }
        else
        {
            RecordSet[0].Dispose(); //  Bitmap destroyed!
            RecordSet.Remove(RecordSet[0]);

        }
        //GC.Collect();
    }

テスト結果:CPU使用率2-3%


1

もう1つの理由は、USB COMポートでSerialPortを開いてから、USBデバイスを取り外した場合です。SerialPortが開かれたため、リソースはシステムのレジストリで以前に接続されたポートへの参照を保持します。システムのレジストリには古いデータが含まれるため、使用可能なポートのリストは正しくありません。したがって、ポートを閉じる必要があります。

ポートでSerialPort.Close()を呼び出すと、オブジェクトでDispose()が呼び出されますが、ガベージコレクションが実際に実行されるまでメモリに残っているため、ガベージコレクターがリソースを解放するまでレジストリは古くなります。

https://stackoverflow.com/a/58810699/8685342から:

try
{
    if (port != null)
        port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
}

port = null;

0

これは問題とは関係ありませんが、.NETのXSLT変換(XSLCompiledTranform)の場合、選択肢がない可能性があります。別の候補は、MSHTMLコントロールです。



0

GCを呼び出す適切な理由の1つは、Raspberry PI(monoで実行)のように、メモリの少ない小さなARMコンピューター上にあります。未割り当てのメモリフラグメントがシステムRAMを使いすぎると、Linux OSが不安定になる可能性があります。メモリオーバーフローの問題を取り除くために毎秒GCを呼び出さなければならない(!)アプリケーションがあります。

別の良い解決策は、オブジェクトが不要になったときにオブジェクトを破棄することです。残念ながら、これは多くの場合それほど簡単ではありません。


0

Small object heap(SOH)とLarge object heap(LOH)があるため

GC.Collect()を呼び出してSOPの逆参照オブジェクトをクリアし、生存しているオブジェクトを次世代に移動できます。

.net4.5では、largeobjectheapcompactionmodeを使用してLOHを圧縮することもできます。


0

多くの新しいSystem.Drawing.Bitmapオブジェクトを作成している場合、ガベージコレクターはそれらをクリアしません。最終的にGDI +はメモリが不足していると判断し、「パラメータが無効です」という例外をスローします。GC.Collect()頻繁に(あまり頻繁にではなく)呼び出すと、この問題が解決するようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.