Javaでのガベージコレクションと循環参照
1. 概要
このクイック記事では、JVMが到達不能であるが循環的な参照を確実に収集する方法を説明します。
まず、さまざまなタイプのGCアルゴリズムについて説明します。 その後、JVMで循環参照がどのように処理されるかを確認します。
また、GCはJVM仕様の一部ではなく、実装者の裁量に委ねられていることにも言及する価値があります。 したがって、各JVM実装には、異なるGC戦略がある場合と、まったくない場合があります。
この記事では、特定のJVM実装であるHotSpotJVMに焦点を当てています。 また、記事全体でJVMとHotSpotJVMの用語を同じ意味で使用する場合があります。
2. 参照カウント
参照カウントGCアルゴリズムは、参照カウントを各オブジェクトに関連付けます。 これらのアルゴリズムは、オブジェクトへの参照の数がゼロより大きい限り、オブジェクトが生きていると見なします。 通常、ランタイムは参照カウントをオブジェクトヘッダーに保存します。
非常に単純な実装では、オブジェクトへの新しい参照ごとに、アトミック参照カウントの増分がトリガーされます。 同様に、新しい間接参照はそれぞれ、アトミックデクリメントをトリガーする必要があります。
Swiftプログラミング言語は、メモリ管理に参照カウントの形式を使用します。 また、JVMには参照カウントに基づくGCアルゴリズムはありません。
2.1. 長所と短所
明るい面としては、定期的なGCの一時的な中断が(ほとんど)ないため、参照カウントによってアプリケーションのライフサイクル全体にメモリ管理コストを分散させることができます。 また、参照カウントがゼロになり、ゴミになるとすぐにオブジェクトを破壊する可能性があります。
参照カウントもフリーランチではありません。 単純な実装では、参照カウントをアトミックにインクリメントまたはデクリメントする必要があるため、参照カウントの更新は非効率的です。 遅延またはバッファリングされた参照カウントアプローチなど、この点で参照カウントをより効率的にすることができる最適化はほとんどありません。
ただし、参照カウントにはまだ1つの重大な問題があります。循環参照を再利用できない。
たとえば、オブジェクトAがオブジェクトBを参照し、その逆も同様であるとします。 AとBがオブジェクトグラフの残りの部分から到達できなくなったとしても、それらの参照カウントがゼロに達することはありません。 それは、彼らがまだお互いへの参照を保持しているためです。
結局のところ、これらの種類の循環参照は、コンピュータサイエンスではかなり一般的です。 たとえば、次の二重リンクリストについて考えてみましょう。 最初に、別のオブジェクトにリストへの参照があります。
リンクリストはオブジェクトD、から到達可能であるため、収集されるべきではなく、参照カウントはこの予想に沿っています。 ここで、オブジェクトD自体が到達不能になったとします。
リンクリストにもアクセスできなくなりましたが、そのコンポーネントの参照数は複数です。 したがって、この単純な参照カウントの実装では、であっても、ランタイムはこのリンクリストをガベージとは見なしません。
3. GCのトレース
トレースコレクターは、GCルートと呼ばれるルートオブジェクトのセットからオブジェクトをトレースすることにより、オブジェクトの到達可能性を判断します。 オブジェクトがルートオブジェクトから直接または間接的に到達可能である場合、そのオブジェクトは生きていると見なされます。 他のものは到達不能であり、収集の候補です:
簡単なトレースコレクターの仕組みは次のとおりです。 GCルートから開始して、訪問する灰色のオブジェクトがなくなるまで、オブジェクトグラフを再帰的にトラバースします。 結局、それはすべての白いオブジェクトが到達不能であり、収集の候補であると見なします。 これは、3色のマーキングアルゴリズムの簡単な描写です。
GCルートは、生きていると確信しているオブジェクトと考えることができます。 たとえば、これらはJavaとJVMのいくつかのGCルートです。
- ローカル変数またはスタックフレームが現在参照しているもの。 これらの変数は現在実行中のメソッドで使用されているため、収集したくありません
- ライブスレッド
- 静的変数
- システムクラスローダーによってロードされたクラス
- JNIローカルおよびグローバル
参照カウントコレクターとは対照的に、トレースコレクターは定期的に収集プロセスを実行します。 したがって、ほとんどの場合、割り当てと割り当ては高速に機能するはずです。 ただし、GCが開始されると、いくつかの問題が発生する可能性があります。
明るい面では、これらのGCアルゴリズムは循環参照の影響を受けません。 各オブジェクトへの参照をカウントする代わりに、GCルートからオブジェクトグラフをトラバースします。 したがって、循環参照がいくつかある場合でも、上の図に示すように、オブジェクトが到達不能である限り、オブジェクトは収集されます。
非常に興味深いことに、バックアップトレースコレクターを参照カウントGCと組み合わせて使用することは、参照カウントの循環参照を修正するための従来のアプローチの1つです。
3.1. HotSpot JVM
HotSpot JVMのすべてのGC実装は、この記事の執筆時点では、CMS、G1、およびZGCを含むトレースコレクターです。 したがって、JVMは循環参照の問題に悩まされることはありません。 これが、この記事からの重要なポイントです。
4. 結論
このクイック記事では、JVMが循環参照を処理する方法を説明しました。
ガベージコレクションの詳細については、ガベージコレクションハンドブックを確認することを強くお勧めします。