JVM ガベージ コレクション シリーズの GCRoot アルゴリズムの実装

順序

友人たちと建国記念日を祝いましょう
ここに画像の説明を挿入

序章

この記事では、HotSpot 仮想マシンがルート到達可能性アルゴリズム (GCROOT) を実装する方法を詳しく紹介します。

参考書籍:『よくわかるJava仮想マシン』

個人的な Java 知識共有プロジェクト - gitee アドレス

個人的な Java 知識共有プロジェクト - github アドレス

ルートノードを列挙する

到達可能性解析において GC ルートノードから参照チェーンを見つける操作を例にとると、GC ルートとして使用できるノードは主にグローバル参照 (定数やクラスの静的属性など) と実行コンテキスト (ローカル変数テーブルなど) です。スタックフレーム内)) )、多くのアプリケーションではメソッド領域が数百メガバイトしかないため、ここでの参照を 1 つずつ確認しようとすると、必然的に時間がかかります。

さらに、実行時間に対する到達可能性分析の感​​度は GC の一時停止にも反映されます。これは、この分析は一貫性を確保できるスナップショットで実行する必要があるためです。ここでの「一貫性」とは、実行システム全体が特定の時点でフリーズしているように見えることを意味します。この点を満たさない場合、解析結果の精度は保証できません。これは、GC の進行中 (Sun はこれを「Stop The World」と呼んでいます) にすべての Java 実行スレッドを停止する必要がある重要な理由の 1 つであり、(ほとんど) 一時停止がないと主張する CMS コレクターでも同様です。ルートノードを列挙するときに一時停止します。

現在主流の Java 仮想マシンは正確な GC を使用しているため、実行システムが停止したときにすべての実行コンテキストとグローバル参照の場所を漏れなく確認する必要はなく、オブジェクト参照がどこに保存されているかを仮想マシンが直接知る方法を備えている必要があります。 。HotSpotの実装では、この目的を達成するためにOopMapと呼ばれる一連のデータ構造が使用され、クラスのロードが完了すると、HotSpotはオブジェクト内のどのオフセットにどのようなデータが存在するかを計算し、JITでコンパイルする処理中に、スタックおよびレジスタ内のどの位置が参照であるかも、特定の位置に記録されます。このようにして、GC はスキャン時に情報を直接知ることができます。以下は、HotSpot クライアント VM によって生成された String.hashCode() メソッドのローカル コードです。0x026eb7a9 の呼び出し命令には、スタック内のオフセット 16 にある EBX レジスタとメモリ領域を示す OopMap レコードがあることがわかります。通常のオブジェクト ポインター (Ordinary Object Pointer) への参照、有効範囲は呼び出し命令から 0x026eb730 (命令ストリームの開始位置) + 142 (OopMap レコードのオフセット) = 0x026eb7be までです。 HLT命令。

ここに画像の説明を挿入

安全なポイント

OopMap の支援により、HotSpot は GC ルートの列挙を迅速かつ正確に完了できますが、非常に現実的な問題が発生します。命令ごとに参照関係の変更や、OopMap の内容の変更を引き起こす可能性のある命令が多数存在します。どちらも対応する OopMap を生成しますが、大量の追加スペースが必要となるため、GC のスペース コストが非常に高くなります。

実際、HotSpot は命令ごとに OopMap を生成しません。前述したように、この情報は「特定の場所」にのみ記録されます。これらの場所は安全ポイント (Safepoint) と呼ばれます。プログラムが実行されます。GC を開始するために一時停止できますが、安全なポイントに到達した場合にのみ一時停止できます。セーフポイントの選択は、小さすぎて GC の待機時間が長すぎたり、頻度が高すぎて実行時の負荷が過度に増加したりすることはできません。したがって、安全点の選定は基本的に「プログラムが長時間実行できる特性を持っているか」という基準に基づいて行われます。各命令の実行時間は非常に短いため、長時間実行できるプログラムである可能性は低いです。 「長時間実行」の最も明白な特徴は、メソッド呼び出し、ループ ジャンプ、例外ジャンプなどの命令シーケンスが多重化されていることです。そのため、これらの関数を持つ命令はセーフポイントを生成します。

Sefepoint の場合、考慮する必要があるもう 1 つの問題は、すべてのスレッド (JNI 呼び出しを実行するスレッドを除く) を最も近い安全なポイントまで「実行」し、GC が発生したときに停止する方法を作成する方法です。選択できるオプションは 2 つあります: プリエンプティブ サスペンドとボランタリー サスペンド。プリエンプティブ サスペンドでは、スレッド実行コードの積極的な連携は必要ありません。GC が発生すると、スレッドが中断されている場所が判明した場合、最初にすべてのスレッドが中断されます。安全なポイントにない場合は、スレッドを再開して安全なポイントまで「実行」させてください。GC イベントに応じてスレッドを一時停止するためにプリエンプティブ割り込みを使用する仮想マシン実装はほとんどありません。

アクティブ割り込みの考え方は、GC がスレッドに割り込む必要がある場合、スレッドに対して直接操作するのではなく、単にフラグを設定するだけであり、各スレッドは実行時と割り込みフラグが見つかったときにこのフラグをアクティブにポーリングするというものです。実際には、それ自体が中断され、一時停止されます。ポーリング フラグがセーフ ポイントと一致する場所に加え、オブジェクトを作成するためにメモリを割り当てる必要がある場所。次のコードのテスト コマンドは、HotSpot によって生成されたポーリング コマンドです。スレッドを一時停止する必要がある場合、仮想マシンは 0x160100 のメモリ ページを読み取り不可に設定します。スレッドがテスト コマンドを実行すると、セルフトラップが生成されます例外信号: スレッドは待機を実装するために例外ハンドラー内で一時停止されるため、1 つのアセンブリ命令がセーフ ポイント ポーリングを完了し、スレッド割り込みをトリガーします。

ここに画像の説明を挿入

安全地帯

Safepoint を使用することで、GC にどうやって入るかという問題は完全に解決されたように見えますが、実際は必ずしもそうではありません。セーフポイント メカニズムにより、プログラムの実行時に、短期間で GC に入ることができるセーフポイントに遭遇することが保証されます。では、プログラムが「実行されていない」場合はどうでしょうか? いわゆるプログラムが実行されていないとは、CPU 時間が割り当てられていないことを意味します。典型的な例としては、スレッドがスリープ状態またはブロック状態にある場合です。 、スレッドは JVM の割り込み要求に応答できず、安全な場所に「歩いて」いきます。割り込みが保留中の場合、JVM はスレッドに CPU 時間が再割​​り当てされるのを待つ可能性が明らかに低くなります。この場合、それを解決するには安全領域(Safe Region)が必要となります。

安全領域とは、コード内で参照関係が変更されないことを意味します。この領域内のどこからでも GC を安全に開始できます。セーフ リージョンを拡張セーフポイントとみなすこともできます。

スレッドが安全領域でコードを実行すると、まず安全領域に入ったことをマークします。そのため、JVM がこの期間中に GC を開始するときに、自分自身を安全領域状態としてマークするスレッドについて心配する必要はありません。 。スレッドが安全領域から出ようとしているとき、システムがルート ノード (または GC プロセス全体) の列挙を完了したかどうかをチェックします。完了している場合、スレッドは実行を続行します。そうでない場合は、スレッドが完了するまで待機する必要があります。安全な領域を離れるための安全な信号を受信します。

この記事の GCRoot アルゴリズムの実装は、書籍「Java 仮想マシンの徹底理解」からの抜粋です。

おすすめ

転載: blog.csdn.net/a_ittle_pan/article/details/127195130