コンカレントガベージコレクターは変数をどのように処理しますか?


8

それが並行マークアンドスイープガベージコレクタであるとしましょう。

そのようなGCが定数ポインターを処理するとき、それはそれらを(ルートから開始して)ウォークスルーし、遭遇したすべてのデータブロックにマークを付けます。次に、マークされていないものをすべてスイープします。クライアントコードは、使用するデータブロックをルートとしてマークする必要があります。

しかし、変数をどうすればよいでしょうか。ここに状況があります:

  1. Vオブジェクトへのポインタを格納する変数ですA
  2. Thread 1読み取りVと中断。
  3. Thread 2変更しV、オブジェクトを指すようにしますB
  4. ガベージコレクターは、「マーク」フェーズを実行し、A参照されなくなった遭遇した後、「スイープ」フェーズ中に割り当てを解除します。
  5. Thread 1目覚めさせ、ルートとしてマークすることにより(ステップ2でA既に読み取った)使用を試みVます。そしてAもう存在しないので失敗します

それで、これをどのように処理するのですか?

Thread 2置換オブジェクトをマークすることができるA(同様のフラグが新たに割り当てられたオブジェクトのために使用される)特殊なDO-未削除フラグで。しかし、このフラグを削除する必要があるのはいつですか?もちろんそれThread 1は可能です。しかし、Thread 2についてThread 1は何も知らないため、これが行われるかどうかはわかりません。これはA解放されないことにつながる可能性があります。そして、GCがそのフラグを削除する場合、GCがA2回目に実行されたときに何も削除されることを妨げるものはありません...

私が読んだオンザフライのマークアンドスイープガベージコレクターの説明では、置き換えられたオブジェクトは「グレー表示」である必要があると述べています。しかし、詳細はありません。ソリューションのより詳細な説明へのリンクをいただければ幸いです。

回答:


4

ガベージコレクターの実装の正確な詳細によっては、ステップ4でこれがまったく問題にならない場合があります。たとえば、ステップ2で、スレッド1はおそらくVレジスターに読み取ります。ガベージコレクターは、おそらくすべてのアクティブな(実行中および一時停止中の)スレッドのレジスターの内容を調べて、レジスターに保持されているオブジェクトへの参照があるかどうかを確認する必要があります。

必然的に、ガベージコレクターの実装は、それが実行されるオペレーティング(およびスレッド化)環境に密接に結合されます。すべての保存された参照と一時的な参照が確実に考慮されるようにするための多くの実装手法があります。


しかし、プラットフォームに依存しない方法でそれを行う方法はありますか?レジスタには、ポインタのように見えるだけのデータが含まれている場合がありますが、実際はそうではありません。私のGC実装は正確であり、GCが処理するすべてのポインターが特定の構造のデータブロックを指すという事実に依存しています。
lorus

うーん、でもこれはアイデアです!そのようなポインタをスタック(またはスレッドローカル変数)の既知の場所に配置して、GCにそれを調べさせることができます。
lorus

@lorus-多くのガベージコレクターの場合、コンパイラーによって生成されたテーブルは、メソッドの任意の時点でポインターを含むレジスターをGCに通知します。それ以外の場合、GCはレジスタの内容を暗黙的に無効にする「安全なポイント」でのみ実行されます。また、GCの実装は、本質的にあるレベルでプラットフォームに依存しています。
スティーブンC

@StephenCええと、ガベージコレクションのような汎用アルゴリズムがプラットフォームに依存している必要があるとは思いません。アトミック操作とメモリバリア-はい。しかし、レジスタへの直接アクセス?いいえ、そうは思いません。効率的ですが絶対に必要というわけではありません。このようなプラットフォームに依存しないアルゴリズムについて詳しく知りたいのですが。
lorus

0

マークフェーズ中にローカル変数をマークする必要があります。通常スタック上に存在するものを含むすべてのローカル変数。何とかして。

さらに、変更されたオブジェクトをスキャンする同期(すべてのミューテーターが停止)フェーズ中に実行する必要があると思います。実際、ローカル変数/レジスターを考慮しなくても、同じ問題が発生する可能性があります。オブジェクトAがnullを指し、オブジェクトBがCを指しているとします。オブジェクトAをスキャンすると、ミューテータースレッドが来て、Cへの参照をBからAにコピーし、Bをnullにします。あなたの指の下で。

ミューテーターの停止を必要としない、これに対処する方法については知りません。通常の手法は、マークフェーズの最後にあり、すべてのミューテーターを停止し、メインマーキングフェーズ中に変更したすべてのオブジェクトに再度マークを付けます。その中にスタックとレジスタを含めます。

レジスターのマーキングは通常、時々スレッドでコレクターを呼び出すことにより同期的に行うことで回避されます。コレクター関数内では、それ自体のローカル変数(ルートではない)のみがレジスターに入れることができ、呼び出しチェーン内の他のすべてのローカル変数はスタック上にあるため、スタックをウォークできます。

または、スレッドにシグナルを送信することもできます。シグナルハンドラーは再びスタック上のすべての変数を強制するので、それらをウォークできます。この方法の欠点は、プラットフォームに依存することです。


はい。しかし、aが話していた状況はローカル変数に関するものではありません。これはグローバルなものであり、異なるスレッドによって同時にアクセスおよび変更できます。
lorus

thread1がVを読み取った後、Aがthread1のローカル変数にない場合、thread2がVを変更した後、Aに到達できないため、とにかくコレクションの準備ができます
ラチェットフリーク

@lorus:スレッドが何かをレジスターにロードする場合、レジスターはローカル変数です。高レベルのソースで言及されているか、コンパイラによって追加されているかは関係ありません。ローカル変数として扱うだけです。
Jan Hudec 2013年

@JanHudecわかりました。今私はそれを手に入れました。それで、(クライアント)スレッドでコレクターを呼び出すことは、実際にはある種のグローバルロックですか?
lorus

1
@JanHudecスレッドがGCで動作し、to-markキューにオブジェクトを追加できる場合を除き、スレッドが参照を読み取るたび
ラチェットフリーク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.