C#でガベージコレクションを強制するためのベストプラクティス


118

私の経験では、ほとんどの人がガベージコレクションを強制するのは賢明ではないと言っているようですが、常に0世代で収集されるわけではないがメモリが問題になる大きなオブジェクトを操作している場合があります。強制的に収集してもよろしいですか?そのためのベストプラクティスはありますか?

回答:


112

ベストプラクティスは、ガベージコレクションを強制しないことです。

MSDNによると:

「収集を呼び出すことでガベージコレクションを強制することは可能ですが、ほとんどの場合、パフォーマンスの問題が発生する可能性があるため、これを回避する必要があります。」

ただし、コードを確実にテストして、Collect()の呼び出しが悪影響を及ぼさないことを確認できる場合は、先に進んでください...

不要になったオブジェクトがクリーンアップされていることを確認してください。カスタムオブジェクトがある場合は、「usingステートメント」とIDisposableインターフェイスの使用を見てください。

このリンクには、メモリ/ガベージコレクションなどの解放に関するいくつかの実用的なアドバイスがあります。

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx



4
オブジェクトがアンマネージメモリを指している場合は、GC.AddMemoryPressure Api(msdn.microsoft.com/en-us/library/…)を通じてガベージコレクターに通知できます。これにより、収集アルゴリズムに干渉することなく、ガベージコレクターにシステムに関する詳細情報が提供されます。
Govert

+1:*「usingステートメント」とIDisposableインターフェイスの使用を確認します。*最後の手段として以外は強制することも考慮しません-良いアドバイス(「免責事項」と読みます)。しかし、私はユニットテストでコレクションを強制的に実行して、バックエンド操作でアクティブな参照が失われることをシミュレートします-最終的にをスローしTargetOfInvocationNullExceptionます。
IAbstract

33

このように見てください。ゴミ箱が10%になったときに台所のゴミを捨てるか、ゴミを出す前にいっぱいにしておく方が効率的ですか。

いっぱいにならないようにすることで、外のゴミ箱に出入りする時間を無駄にすることになります。これは、GCスレッドの実行時に何が起こるかに似ています。すべての管理対象スレッドは、実行中に中断されます。そして、私が間違っていなければ、GCスレッドは複数のAppDomain間で共有される可能性があるため、ガベージコレクションはそれらすべてに影響します。

もちろん、すぐにゴミ箱に何も追加しないという状況に遭遇するかもしれません-たとえば、休暇を取るつもりなら。その後、出かける前にゴミを捨てるのがいいでしょう。

これは、GCを強制することが役立つ可能性がある1回になる可能性があります。プログラムがアイドル状態の場合、割り当てがないため、使用中のメモリはガベージコレクションされません。


5
1分以上放置すると死ぬ赤ちゃんがいて、ゴミを処理するのに1分しかなかった場合、一度に全部ではなく、毎回少しずつやりたいと思います。残念ながら、GC :: Collect()メソッドは、頻繁に呼び出すほど高速にはなりません。したがって、リアルタイムエンジンの場合、破棄メカニズムを使用してGCにデータをプールさせることができない場合は、管理されたシステムを使用しないでください-私の回答に従ってください(おそらくこれより下です、笑)。
ジン

1
私の場合、A *(最短パス)アルゴリズムを繰り返し実行してパフォーマンスを調整しています...これは、本番環境では(各「マップ」で)1回だけ実行されます。したがって、各「マップ」をナビゲートした後に強制することができる/すべきである本番環境の状況をより厳密にモデル化しているように感じるので、「パフォーマンス測定ブロック」の外で、各反復の前にGCを実行したいと思います。
corlettk 2012

測定されたブロックの前にGCを呼び出しても、実際の状況はモデル化されません。これは、本番環境ではGCが予測できない時間に実行されるためです。これを軽減するには、いくつかのGC実行を含む長い測定を行い、GC中のピークを分析と統計に含める必要があります。
2012

32

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

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

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

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

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

もう1つのケースは、数分ごとに起動していくつかのアイテムを処理し、スリープ状態の間は状態を維持しないサービスです。それからちょうど寝る前にフルコレクションを強制するかもしれませんなるは価値があります。

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

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

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


2
類推:Playschool(システム)はクレヨン(リソース)を保持します。子供(タスク)の数と色の不足に応じて、教師(.Net)は子供間での割り当てと共有の方法を決定します。珍しい色が要求されると、教師はプールから割り当てるか、使用されていない色を探すことができます。教師は、未使用のクレヨンを定期的に収集して(ガベージコレクション)、物事を整理しておく(リソースの使用を最適化する)裁量があります。一般的に、親(プログラマー)は、クラスルームクレヨンの整理整頓に関するポリシーを事前に決定することはできません。1人の子供の予定された昼寝は、他の子供たちの色付けを妨げる良い機会とはなりそうにありません。
AlanK

1
@AlanK、私はこれが好きです。子供たちがその日に帰宅するとき、子供たちが邪魔することなく、教師のヘルパーがきちんと片付けをするのにとても良い時間です。(私が取り組んだ最近のシステムは、GCを強制するのではなく、サービスプロセスを再起動したばかりです。)
Ian Ringrose

21

Rico Marianiの例は良かったと思います。アプリケーションの状態に大きな変化がある場合は、GCをトリガーするのが適切な場合があります。たとえば、ドキュメントエディターでは、ドキュメントが閉じられたときにGCをトリガーしても問題ありません。


2
または、失敗の履歴を示し、その粒度を上げるための効率的な解決策を示さない大きな連続したオブジェクトを開く直前。
crokusek 2013年

17

絶対的なプログラミングの一般的なガイドラインはほとんどありません。半分の時間、誰かが「あなたはそれを間違っている」と言うとき、彼らはある量の教義を噴出しているだけです。Cでは、自己変更コードやスレッドなどを恐れていましたが、GC言語では、GCを強制するか、GCの実行を妨げています。

ほとんどのガイドラインと優れた経験則(および優れた設計手法)の場合と同様に、確立された基準を回避することが理にかなっていることはまれです。あなたは、あなたがその事件を理解していること、あなたの事件が本当に一般的な慣行の廃止を必要としていること、そしてあなたが引き起こし得るリスクと副作用を理解していることを確信する必要があります。しかし、そのような場合があります。

プログラミングの問題はさまざまで、柔軟なアプローチが必要です。ガベージコレクションされた言語でGCをブロックすることが理にかなっているケースや、自然に発生するのを待つのではなくトリガーすることが理にかなっている場所を見てきました。95%の確率で、これらのどちらかが問題の権利に近づかなかったという標識になります。しかし、20回に1回は、おそらくそれを正当化する正当なケースがあります。


12

ガベージコレクションの裏をかこうとしないことを学びました。そうは言っても、usingファイルI / Oやデータベース接続などのアンマネージリソースを処理するときは、キーワードを使用することに固執します。


27
コンパイラ?コンパイラはGCとどのように関係していますか?:)
KristoferA

1
何もありません。コードをコンパイルして最適化するだけです。そして、これは間違いなくCLR ...や.NETとも関係ありません。

1
非常に大きな画像など、大量のメモリを占有するオブジェクトは、明示的にガベージコレクションしない限り、ガベージコレクションされない可能性があります。これ(大きなオブジェクト)は、多かれ少なかれOPの問題だったと思います。
code4life 2013

1
usingでラップすると、usingスコープから外れると、GCにスケジュールされます。コンピュータが爆発しない限り、そのメモリはおそらくクリーンアップされます。
2013

9

それがベストプラクティスであるかどうかはわかりませんが、大量の画像をループで処理する場合(つまり、多くのGraphics / Image / Bitmapオブジェクトを作成して破棄する場合)、定期的にGC.Collectを使用します。

私がどこかで読んだのは、プログラムが(ほとんど)アイドル状態で、集中ループの途中ではないときにGCが実行されるため、手動のGCが理にかなっている領域のように見えることです。


これが必要ですか?コードがアイドル状態でなくても、メモリが必要な場合はGC 収集します。
Konrad Rudolph、

現在.net 3.5 SP1でどのようになっているのかはわかりませんが、以前(1.1と2.0に対してテストしたと思います)、メモリ使用量に違いがありました。必要なときにGCはもちろん、常に収集の意志が、あなたはまだあなたが唯一の20でしょうが、にもかかわらずfew'moreテストを必要とする必要があるときにRAMの100のメグスを無駄に終わるかもしれない
マイケルStum

2
GCは、「何かがアイドル」のときではなく、世代0が特定のしきい値(たとえば1MB)に達したときにメモリ割り当てでトリガーされます。そうしないと、オブジェクトを割り当ててすぐに破棄するだけで、ループ内でOutOfMemoryExceptionが発生する可能性があります。
liggett78 2008年

5
100メガのRAMは、他のプロセスがそれを必要としないのであれば無駄になりません。パフォーマンスが大幅に向上します:-P
Orion Edwards

9

手動での呼び出しを必要とする最近遭遇した1つのケースはGC.Collect()、小さなマネージC ++オブジェクトにラップされた大きなC ++オブジェクトを使用していて、C#からアクセスされる場合でした。

使用されるマネージメモリの量はごくわずかであるため、ガベージコレクタは呼び出されませんでしたが、使用されるアンマネージメモリの量は膨大でした。Dispose()オブジェクトを手動で呼び出すと、オブジェクトが不要になったときに自分で追跡する必要がありますが、呼び出しをGC.Collect()行うと、参照されなくなったオブジェクトはすべてクリーンアップされます。


6
これを解決するより良い方法GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)は、コンストラクタで呼び出し、後でGC.RemoveMemoryPressure(addedSize)ファイナライザで呼び出すことです。このようにして、収集できるアンマネージ構造のサイズを考慮して、ガベージコレクターが自動的に実行されます。stackoverflow.com/questions/1149181/...
HugoRune

そして、問題を解決するさらに良い方法は、実際にDispose()を呼び出すことです。
fabspro 2014年

2
最良の方法は、Using構造を使用することです。Try / Finally .Disposeは面倒です
TamusJRoyce

7

あなたはすでにベストプラクティスをリストアップしていると思いますが、本当に必要でない限りそれを使用するべきではありません。最初にこれらの質問に答える必要がある場合は、プロファイリングツールを使用して、コードを詳細に確認することを強くお勧めします。

  1. 必要以上に大きなスコープでアイテムを宣言しているコードに何かありますか
  2. メモリ使用量は本当に高すぎますか
  3. GC.Collect()を使用する前後のパフォーマンスを比較して、本当に役立つかどうかを確認します。

5

プログラムにメモリリークがなく、オブジェクトが蓄積され、Gen 0でGCできないと仮定します。理由は次のとおりです。1)それらは長期間参照されるため、Gen1とGen2に入る。2)それらは大きなオブジェクト(> 80K)なので、LOH(Large Object Heap)に入ります。また、LOHは、Gen0、Gen1、Gen2のように圧縮を行いません。

「.NETメモリ」のパフォーマンスカウンターを確認すると、1)問題は本当に問題ではないことがわかります。一般に、10 Gen0 GCごとに1 Gen1 GCがトリガーされ、10 Gen1 GCごとに1 Gen2 GCがトリガーされます。理論的には、GC0に圧力がない場合(プログラムのメモリ使用量が実際に配線されている場合)、GC1とGC2をGCすることはできません。それは私には決して起こりません。

問題2)については、「。NETメモリ」パフォーマンスカウンターをチェックして、LOHが肥大化しているかどうかを確認できます。これが実際に問題の問題である場合は、おそらくこのブログでhttp://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspxに示されているように、ラージオブジェクトプールを作成できます。


4

ラージオブジェクトは、gen 0ではなくLOH(ラージオブジェクトヒープ)に割り当てられます。gen0でガベージコレクションされないという場合は、そのとおりです。完全なGCサイクル(世代0、1、2)が発生したときにのみ収集されると思います。

そうは言っても、大きなオブジェクトを操作していてメモリのプレッシャーが高まっている場合、GCはメモリをより積極的に調整および収集することになると思います。

収集するかどうか、どのような状況かはわかりにくい。多数のコントロールなどを含むダイアログウィンドウ/フォームを破棄した後、GC.Collect()を使用していました(フォームとそのコントロールが第2世代になるまでには、ビジネスオブジェクトの多数のインスタンスを作成する/大量のデータをロードするためです-いいえ大きなオブジェクトは明らかに)、しかし実際にそうすることによって長期的にはプラスまたはマイナスの影響に気づかなかった。


4

追加したいのは、GC.Collect()(+ WaitForPendingFinalizers())の呼び出しはストーリーの一部です。他の人が正しく述べているように、GC.COllect()は非決定的なコレクションであり、GC自体(CLR)の裁量に任されています。WaitForPendingFinalizersの呼び出しを追加しても、確定的ではない場合があります。このmsdn リンクからコードを取得し、オブジェクトループの反復を1または2としてコードを実行します。非決定論的な意味がわかります(オブジェクトのデストラクタにブレークポイントを設定します)。正確には、Wait ..()によって残留オブジェクトが1つ(または2)しかなかった場合、デストラクタは呼び出されません。[引用要求]

コードがアンマネージリソース(例:外部ファイルハンドル)を処理している場合は、デストラクタ(またはファイナライザ)を実装する必要があります。

ここに興味深い例があります:

:MSDNの上記の例をすでに試した場合、次のコードで問題を解決できます。

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

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

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

まず、出力を分析し、次に実行してから、以下の理由をお読みください。

{デストラクタは、プログラムが終了すると暗黙的に呼び出されます。オブジェクトを確定的にクリーンにするために、IDisposableを実装し、Dispose()を明示的に呼び出す必要があります。それが本質です!:)


2

さらに、GC Collectを明示的にトリガーしても、プログラムのパフォーマンスが向上しない場合があります。それを悪化させることはかなり可能です。

.NET GCは適切に設計され、適応できるように調整されています。つまり、プログラムのメモリ使用量の「習慣」に応じてGC0 / 1/2しきい値を調整できます。したがって、しばらく実行すると、プログラムに適合します。GC.Collectを明示的に呼び出すと、しきい値がリセットされます!そして.NETは、プログラムの「習慣」に再び適応するために時間を費やす必要があります。

私の提案は常に.NET GCを信頼することです。メモリの問題が発生した場合は、「。NETメモリ」パフォーマンスカウンターを確認し、自分のコードを診断します。


6
この回答を以前の回答とマージする方が良いと思います。
Salamander2007

1

それがベストプラクティスかどうかわからない...

提案:不明な場合は、これを実装しないでください。事実が判明したときに再評価し、パフォーマンステストの前後を実行して確認します。


0

ただし、コードを確実にテストして、Collect()の呼び出しが悪影響を及ぼさないことを確認できる場合は、先に進んでください...

私見、これは「プログラムに将来バグがないことを証明できたら、先に進んでください...」と同じです。

真剣に考えると、GCの強制はデバッグやテストの目的で役立ちます。それ以外のときに実行する必要があると思われる場合は、誤っているか、プログラムが正しく構築されていません。いずれにせよ、ソリューションはGCを強制していません...


「それでは、あなたが間違っているか、プログラムが間違って構築されています。どちらにせよ、解決策はGCを強制するものではありません...」それは理にかなっているいくつかの例外的な状況があります。
ロール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.