ガベージコレクションと世代について少し

誰もが、最新のシステムのほとんどが世代を使用してガベージコレクションの効率を高めていることを知っています。 しかし、これらの世代が一般的にどのように機能し、どのようにパフォーマンスを向上させるのか疑問に思ったことはありますか? しかし、自分より先に進んで、順番にそれを取らないようにしましょう。



そのため、最新のガベージコレクター(GC)システムのほとんどは、世代を使用して短命のオブジェクトをより効率的にリリースします。 ヒューリスティックルールがあります。これは、新しく作成されたオブジェクトのほとんどが非常に短時間使用され、できるだけ早く安全に削除できることを示しています。



世代の主な考え方は、マネージヒープのすべてのオブジェクト(非常に多くの場合があります)ではなく、この若い世代のオブジェクトのみを分析するため、若い世代の「ガベージ」をより頻繁に、より速く収集することです。



そのような例を見てみましょう。 遅延方法でプロパティを初期化するオブジェクト「A」があるとします。



public class B { } public class A { private Lazy<B> _lazyB = new Lazy<B>( () => new B()); public BB { get { return _lazyB.Value; } } }
      
      







そして、オブジェクト「A」が第2世代であっても、プロパティBへのアクセス、したがってこのオブジェクトの作成は発生します。



 var a = new A(); GC.Collect(); GC.Collect(); // output: A resides in Gen 2, AB resides in Gen 0 Console.WriteLine("A resides in Gen {0}, AB resides in Gen {1}", GC.GetGeneration(a), GC.GetGeneration(aB)); GC.Collect();
      
      







したがって、ゼロ世代のガベージコレクションは、この世代のオブジェクトのみの分析で構成されます。 つまり、行(3)でガベージコレクションを開始すると、新しく作成されたオブジェクト「B」を含め、ゼロ世代のすべてのオブジェクトが到達不能としてマークされます。 次に、すべてのルートリンクを分析して、この世代のオブジェクトの到達可能性を判断します。 オブジェクトが到達可能である場合、そのオブジェクトは生きていると見なされ、ルートリンクからアクセスできない他のすべてのオブジェクトはゴミと見なされて削除されます。







ただし、この場合、オブジェクト「B」はルートリンクから直接到達できません。つまり、その到達範囲を判断するには、アプリケーションのすべてのヒープ内のすべてのオブジェクトのフィールドを分析する必要があります。そうしないと、新しく作成されたオブジェクトがガベージコレクタによって「誤って」収集される可能性がありますこれは明らかに望ましくありません。 それでは、ゼロジェネレーションのオブジェクトの到達可能性を判断するためにマネージヒープ全体を全体として分析する必要がある場合、世代のポイントは何ですか?



この問題を解決するには、オブジェクト「B」の到達可能性を判断するために分析する必要があるオブジェクトのリストにオブジェクト「A」を何らかの方法で追加する必要があります。 ただし、すべての「ダーティ」オブジェクトのリストを保持する代わりに、ほとんどの世代がサポートするガベージコレクター実装は、「若い」子孫を作成したオブジェクトのアドレスを格納するカードテーブルと呼ばれる特別なデータ構造を使用します。



カードテーブルは、ビットマスクである単純なデータ構造です。各ビットは、特定の範囲のアドレスにあるオブジェクトが「ダーティ」であり、「若い」オブジェクトへのリンクを含むことを示します。 現時点では、ビットマスクの1ビットは128バイトの範囲を表します。つまり、ビットマスクの各バイトには1Kの範囲に関する情報が含まれています。 このアプローチは、効率と、ガベージコレクタがこのテーブルを最新の状態に保つために必要な追加メモリの量とのトレードオフです。 したがって、ユーザーモードで2 GBのアドレス空間を使用できる32ビットシステムの場合、カードテーブルのサイズは2 MBです。 ただし、カードテーブルの1ビットは128バイトのアドレススペースの範囲をマークするため、ガベージを収集するたびに、若い世代への参照を含まない可能性のある他の多数のオブジェクトを分析する必要があります。







このデータ構造を最新の状態に保つために、オブジェクトフィールドが書き込まれるたびに、JITコンパイラーはいわゆる「書き込みバリア」を生成します。これは、書き込まれるオブジェクトのアドレスが一時セグメントにある場合、カードテーブルの更新に削減され、つまり 0世代または1世代の若いオブジェクトです。



ここで、ケースに戻ると、オブジェクト「B」はガベージコレクターによって収集されません。ルートリンク(参照されていない)が到達可能性分析のために分析されるだけでなく、第2世代の下位128バイトにあるすべてのオブジェクトも分析されるためです。オブジェクト「A」が取得する場所。



なぜこれがすべて必要なのですか?




はい。ガベージコレクションの実装方法に関する情報には、実用的な利点は特にありません(長期間存続するオブジェクトのイベントをサブスクライブし、サブスクライブを解除するまで)。 ガベージコレクションを議論するときは常に世代について言及する必要がありますが、追加のスクラップと有名な母親がいなければ、効率的なガベージコレクションを実現することは不可能だということはほとんどありません。



ちなみに、この実装には小さな実用的な結果があります:古い世代のオブジェクトが新しいオブジェクトを作成し、フィールドにそれほど頻繁に保存しないという事実にもかかわらず(イギリスの科学者は、これを行うのは第2世代のオブジェクトの1%だけです)オブジェクトフィールドへの書き込みには、カードテーブルの更新に必要なハッキングのための追加費用が必要です。 これにより、たとえば、マネージドワールドでのフィールドの記述は、アンマネージドワールド(たとえば、C ++の場合)に比べてわずかに費用のかかる操作になります。



サイトリンク





All Articles