16

SOに関する別の質問*とその後のコメントの議論に答える際に、私ははっきりしない点で壁にぶつかりました。

私が迷っているところはどこでも訂正してください...

ガベージコレクターは、オブジェクトを収集するときに、そのオブジェクトのファイナライザーを別のスレッドで呼び出します(ファイナライザーがDispose()メソッドなどによって抑制されている場合を除く)。収集中、GCは、収集をトリガーしたスレッド(バックグラウンド収集は別として)を除くすべてのスレッドを一時停止します。

明確でないこと:

  1. ガベージコレクターは、ファイナライザーがそのオブジェクトで実行されるのを待ってから収集しますか?
  2. そうでない場合は、ファイナライザーの実行中にスレッドの一時停止を解除しますか?
  3. 待機した場合、ファイナライザーが中断されたスレッドの1つによって保持されているロックに遭遇した場合はどうなりますか?ファイナライザースレッドはデッドロックしますか?(私の答えでは、これは悪い設計であると主張しますが、これが発生する可能性があるケースを見ることができた可能性があります)

*元の質問へのリンク:
.NETGCファイナライザーから同期されたオブジェクトへのアクセス

4

3 に答える 3

53

ガベージコレクターは、ファイナライザーがそのオブジェクトで実行されるのを待ってから収集しますか?

あなたの質問は少し曖昧です。

GCは、ファイナライズが必要な「デッド」オブジェクトに遭遇すると、デッドオブジェクトのストレージを再利用する試みを中止します。代わりに、オブジェクトを「ファイナライズが必要であることがわかっているオブジェクト」のキューに入れ、ファイナライザースレッドが完了するまで、そのオブジェクトを有効なものとして扱います。

したがって、はい、GCはファイナライザーが実行されるまで「待機」してからストレージを再利用します。ただし、同期的に待機しません。「GCはファイナライザーを同期的に呼び出しますか?」と質問しているようです。いいえ、後でファイナライズするオブジェクトをキューに入れ、トラックインを続けます。GCは、適切なプログラムができるだけ早く実行を再開できるように、ガベージを解放してメモリを圧縮するタスクを迅速に実行したいと考えています。クリーンアップされる前に注意を必要としている気まぐれなオブジェクトを処理するのをやめるつもりはありません。そのオブジェクトをキューに入れ、「静かにして、ファイナライザースレッドが後で処理します」と表示します。

後でGCはオブジェクトを再度チェックし、「まだ死んでいますか?そしてファイナライザーを実行しましたか?」と言います。答えが「はい」の場合、オブジェクトは再利用されます。(ファイナライザーは、死んだオブジェクトをライブのオブジェクトに戻す可能性があることを忘れないでください。決してそうしないようにしてください。結果として、楽しいことは何も起こりません。)

ファイナライザーの実行中にスレッドの一時停止を解除しますか?

GCは、フリーズしたスレッドを解凍し、ファイナライザースレッドに「やらなければならない作業があります」と通知すると思います。したがって、ファイナライザスレッドが実行を開始すると、GCによってフリーズされたスレッドが再び起動します。

ファイナライザーは、スレッドに関連付けられたリソースを解放するために、ユーザースレッドへの呼び出しをマーシャリングする必要がある場合があるため、フリーズされていないスレッドが必要になる場合があります。もちろん、これらのユーザースレッドの一部はブロックまたはフリーズされる可能性があります。スレッドは常に何かによってブロックされる可能性があります。

ファイナライザーが中断されたスレッドの1つによって保持されているロックに遭遇した場合はどうなりますか?ファイナライザースレッドはデッドロックしますか?

もちろんです。ファイナライザースレッドには、デッドロックを防ぐ魔法はありません。ユーザースレッドがファイナライザースレッドによって取得されたロックを待機していて、ファイナライザースレッドがユーザースレッドによって取得されたロックを待機している場合、デッドロックが発生しています。

ファイナライザースレッドのデッドロックの例はたくさんあります。他のシナリオへのリンクがたくさんある、そのようなシナリオの1つに関する優れた記事を次に示します。

http://blogs.microsoft.co.il/blogs/sasha/archive/2010/06/30/sta-objects-and-the-finalizer-thread-tale-of-a-deadlock.aspx

記事にあるように、ファイナライザーは非常に複雑で危険なクリーンアップメカニズムであり、可能であれば回避する必要があります。ファイナライザーを間違えるのは信じられないほど簡単で、正しくするのは非常に困難です。

于 2011-03-07T18:27:30.937 に答える
4

ファイナライザーを含むオブジェクトは、長持ちする傾向があります。収集中に、GCがファイナライザーを使用してオブジェクトをガベージとしてマークすると、そのオブジェクトは(まだ)収集されません。GCは、そのオブジェクトを、GCの終了後に実行されるファイナライザーキューに追加します。その結果、このオブジェクトは収集されないため、次の世代に移動します(これにより、参照するすべてのオブジェクトが移動します)。

GCは、実行中のすべてのスレッドを一時停止します。一方、ファイナライザスレッドは、アプリケーションの実行を継続している間、バックグラウンドで実行されます。ファイナライザーは、ファイナライズ用に登録されているすべてのオブジェクトのすべてのファイナライズメソッドを呼び出します。オブジェクトのファイナライザーメソッドが実行された後、オブジェクトはキューから削除され、その時点からオブジェクト(および場合によってはオブジェクトがまだ参照しているすべてのオブジェクト)はガベージになります。そのオブジェクトの生成のオブジェクトをクリーンアップする次のコレクションは、(ついに)そのオブジェクトを削除します。第2世代に存在するオブジェクトは第1世代に存在するオブジェクトの約10分の1、第1世代は第0世代の10分の1で収集されるため、このようなオブジェクトが最終的にガベージコレクションされるまでに時間がかかる場合があります。

ファイナライザースレッドはマネージコードを実行する単純なスレッド(ファイナライザーを呼び出す)であるため、ブロックしたり、デッドロックを発生させたりする可能性があります。このため、ファイナライズメソッドで行うことはできるだけ少なくすることが重要です。ファイナライザーはバックグラウンドスレッドであるため、finalizeメソッドが失敗すると、AppDomain全体がダウンする可能性もあります(うん!)。

このデザインは残念なことと言えますが、考えてみれば、フレームワークが私たちの混乱を効果的にきれいにする他のデザインは想像しがたいものです。

だから、あなたの質問に答えるために:

  1. はい、オブジェクトがファイナライザキューから削除された後でのみ、オブジェクトはガベージになり、GCがそれを収集します。
  2. GCは、ファイナライザキューも含め、すべてのスレッドを一時停止します。
  3. ファイナライザキューがデッドロックする可能性があります。finalizeメソッド内でのロックは可能な限り少なくします。
于 2011-03-07T18:30:45.900 に答える
3

ガベージコレクターは、オブジェクトを4つのグループに分割するものと考えるのが最も簡単です。

  1. ルート化されたオブジェクトが到達できないもの。
  2. ライブのファイナライズ可能なオブジェクトのリストから到達可能であるが、他のルート化されたオブジェクトからは到達できないもの。
  3. ライブのファイナライズ可能なオブジェクトのリストにあるが、そのリスト以外のルート化されたオブジェクトを介して到達可能なもの。
  4. ライブのファイナライズ可能なオブジェクトのリストにはないが、そのリスト以外のルート化されたオブジェクトを介して到達可能なもの。

ガベージコレクタが実行されると、タイプ#1のオブジェクトは表示されなくなります。#2のオブジェクトは、差し迫ったファイナライズが必要なオブジェクトのリストに追加され、「ライブファイナライズ可能なオブジェクト」リストから削除されます(したがって、カテゴリ#4のオブジェクトになります)。ファイナライズが必要なオブジェクトのリストは通常​​のルート参照であるため、このリストにあるオブジェクトを収集することはできませんが、ファイナライザーが完了するまでに他のルート参照が作成されない場合、オブジェクトはカテゴリ#に移動します。 1.1。

于 2011-03-07T18:42:59.213 に答える