1. 序章

このチュートリアルでは、 Javaメモリ管理の基本的な問題と、それを実現するためのより良い方法を常に見つける必要性について説明します。 これは主に、Javaで導入されたShenandoahと呼ばれる新しい実験的なガベージコレクターと、他のガベージコレクターとの比較について説明します。

2. ガベージコレクションの課題を理解する

ガベージコレクタは、JVMのようなランタイムが、その上で実行されているユーザープログラムのメモリの割り当てと再利用を管理する自動メモリ管理の形式です。 ガベージコレクターを実装するためのいくつかのアルゴリズムがあります。 これらには、参照カウント、マークスイープ、マークコンパクト、およびコピーが含まれます。

2.1. ガベージコレクターに関する考慮事項

ガベージコレクションに使用するアルゴリズムに応じて、ユーザープログラムが一時停止されている間に実行することも、ユーザープログラムと同時に実行することもできます。 前者は、ストップザワールドポーズとも呼ばれる長いポーズのために、高いレイテンシを犠牲にして、より高いスループットを実現します。 後者はレイテンシーの向上を目指していますが、スループットが低下します。

実際、現代のコレクターのほとんどはハイブリッド戦略を使用しており、ストップザワールドアプローチとコンカレントアプローチの両方を適用しています。 通常、は、ヒープスペースを若い世代と古い世代に分割することで機能します。 次に、世代別コレクターは、一時停止を減らすために、若い世代ではストップザワールドコレクションを使用し、古い世代では同時コレクションを使用します。

それでも、スイートスポットは、最小限の一時停止で実行され、高スループットを提供するガベージコレクターを見つけることです。これはすべて、ヒープサイズが小さいものから非常に大きいものまで予測可能な動作を示します。 これは、Javaガベージコレクションの革新のペースを初期の頃から生き続けてきた絶え間ない闘争です。

2.2. Javaの既存のガベージコレクター

従来のガベージコレクターには、シリアルコレクターとパラレルコレクターが含まれます。 彼らは世代別のコレクターであり、若い世代ではコピーを使用し、古い世代ではマークコンパクトを使用します。

優れたスループットを提供する一方で、それらは長いストップザワールドポーズの問題に苦しんでいます。

Java1.4で導入されたConcurrentMark Sweep(CMS)コレクターは、世代を超えた同時の低ポーズコレクターです。 これは、若い世代ではコピー、古い世代ではマークスイープで機能します。

ほとんどの作業をユーザープログラムと同時に実行することにより、一時停止時間を最小限に抑えようとします。 それでもなお、には予測できない一時停止につながる問題があり、より多くのCPU時間を必要とし、サイズが4GBを超えるヒープには適していません。

CMSの長期的な代替として、 Garbage First(G1)コレクターがJava7に導入されました。 G1は、世代別、並列、同時、および段階的にコンパクト化する低ポーズコレクターです。 これは、若い世代ではコピー、古い世代ではマークコンパクトで機能します。

ただし、G1は地域化されたコレクターでもあり、ヒープ領域をより小さな領域に構造化します。 これにより、より予測可能な一時停止の利点が得られます。 大量のメモリを搭載したマルチプロセッサマシンを対象としているため、G1も一時停止から解放されません。

したがって、より優れたガベージコレクター、特に一時停止時間をさらに短縮するガベージコレクターを見つけるための競争が続いています。 Z、Epsilon、Shenandoahなど、JVMが最近導入した一連の実験的なコレクターがあります。 それとは別に、G1はさらに改善を続けています。

目的は、一時停止のないJavaに可能な限り近づくことです。

3. シェナンドアガベージコレクター

Shenandoah は、 Java 12で導入され、レイテンシスペシャリストとして位置付けられている実験的なコレクターです。 ユーザープログラムと同時にガベージコレクション作業を行うことで、一時停止時間を短縮しようとします。

たとえば、Shenendoahは、オブジェクトの再配置と圧縮を同時に実行しようとします。 これは基本的に、Shenandoahの一時停止時間がヒープサイズに直接比例しなくなったことを意味します。 したがって、ヒープサイズに関係なく、一貫した低ポーズ動作を提供できます。

3.1. ヒープ構造

Shenandoahは、G1と同様に、地域化されたコレクターです。 これは、ヒープ領域を同じサイズの領域のコレクションに分割することを意味します。 リージョンは、基本的にメモリ割り当てまたは再利用の単位です。

ただし、G1や他の世代のコレクターとは異なり、Shenandoahはヒープ領域を世代に分割しません。 したがって、サイクルごとにほとんどのライブオブジェクトにマークを付ける必要があります。これは、世代別のコレクターが回避できます。

3.2. オブジェクトのレイアウト

Javaでは、メモリ内のオブジェクトにはデータフィールドが含まれるだけでなく、いくつかの追加情報も含まれます。 この追加情報は、オブジェクトのクラスへのポインターを含むヘッダーとマークワードで構成されます。 マークワードには、ポインタの転送、年齢ビット、ロック、ハッシュなど、いくつかの用途があります。

Shenandoah は、このオブジェクトレイアウトに単語を追加します。 これは間接ポインターとして機能し、Shenandoahがオブジェクトへのすべての参照を更新せずにオブジェクトを移動できるようにします。 これはブルックスポインターとしても知られています。

3.3. バリア

ストップザワールドモードで収集サイクルを実行する方が簡単ですが、ユーザープログラムと同時に実行すると、複雑さが増します。 同時マーキングや圧縮など、収集フェーズにさまざまな課題があります。

解決策は、バリアと呼ばれるものを介してすべてのヒープアクセスをインターセプトすることにあります。 ShenandoahおよびG1のような他の同時コレクターは、ヒープの一貫性を確保するためにバリアを利用します。 ただし、バリアはコストのかかる操作であり、一般にコレクターのスループットを低下させる傾向があります。

たとえば、オブジェクトへの読み取りおよび書き込み操作は、バリアを使用してコレクタによって傍受される場合があります。

Shenandoah は、SATBバリア、読み取りバリア、書き込みバリアなど、さまざまなフェーズで複数のバリアを利用します。 これらがどこで使用されているかについては、後のセクションで説明します。

3.4. モード、ヒューリスティック、および障害モード

モードは、Shenandoahの実行方法を定義し、どのバリアを使用するかなどを定義し、パフォーマンス特性も定義します。 利用可能なモードは、ノーマル/ SATB、IU、パッシブの3つです。 通常/SATBモードがデフォルトです。

ヒューリスティックは、コレクションを開始するタイミングと、コレクションに含める必要のあるリージョンを決定します。 これらには、アダプティブ、スタティック、コンパクト、アグレッシブが含まれ、デフォルトのヒューリスティックとしてアダプティブがあります。 たとえば、60%以上のガベージがあるリージョンを選択し、75%のリージョンが割り当てられたときに収集サイクルを開始することを選択できます。

Shenandoahは、ヒープを割り当てるユーザープログラムよりも速くヒープを収集する必要があります。 ただし、が遅れて、障害モードの1つが発生する場合があります。 これらの障害モードには、ペーシング、縮退したコレクション、および最悪の場合は完全なコレクションが含まれます。

4. シェナンドア収集フェーズ

Shenandoahの収集サイクルは、主に3つのフェーズで構成されています。参照のマーク付け、退避、更新です。 これらのフェーズでの作業のほとんどはユーザープログラムと同時に行われますが、ストップザワールドモードで実行する必要のある小さな部分がまだあります。

4.1. マーキング

マーキングは、ヒープ内の到達不能なすべてのオブジェクトまたはその一部を識別するプロセスです。 これを行うには、ルートオブジェクトから開始し、オブジェクトグラフをトラバースして、到達可能なオブジェクトを見つけます。 トラバース中に、各オブジェクトに白、灰色、または黒の3色のいずれかを割り当てます。

ストップザワールドモードでのマーキングは簡単ですが、並行モードでは複雑になります。 これは、マーキングの進行中にユーザープログラムがオブジェクトグラフを同時に変更するためです。 Shenandoahは、Snapshot At the Beginning(SATB)アルゴリズムを使用してこれを解決します。

これは、マーキングの開始時に生きていたオブジェクト、またはマーキングの開始以降に割り当てられたオブジェクトはすべてライブと見なされることを意味します。 Shenandoah は、SATBバリアを使用して、ヒープのSATBビューを維持します。

ほとんどのマーキングは同時に行われますが、ストップザワールドモードで行われる部分もあります。 stop-the-worldモードで発生する部分は、ルートセットをスキャンするinit-markと、保留中のすべてのキューを排出してルートセットを再スキャンするfinal-markです。 最後のマークは、避難する地域を示すコレクションセットも準備します。

4.2. クリーンアップと避難

マーキングが完了すると、ガベージ領域を再利用できるようになります。 ガベージ領域は、ライブオブジェクトが存在しない領域です。 クリーンアップは同時に行われます。

次のステップは、コレクションセット内のライブオブジェクトを他のリージョンに移動することです。 これは、メモリ割り当ての断片化を減らすために行われるため、コンパクトとも呼ばれます。 避難または圧縮は完全に同時に行われます。

さて、これはシェナンドアが他のコレクターと異なるところです。 ユーザープログラムがオブジェクトの読み取りと書き込みを継続するため、オブジェクトの同時再配置には注意が必要です。 Shenandoahは、オブジェクトのブルックスポインターに対してコンペアアンドスワップ操作を実行して、そのto-spaceバージョンを指すようにすることで、これを実現しています。

さらに、Shenandoah は、読み取りおよび書き込みバリアを使用して、同時避難中に厳密な「スペースへの」不変部分が維持されるようにします。 これが意味することは、読み取りと書き込みは、避難後も存続することが保証されているto-spaceから行われる必要があるということです。

4.3. リファレンスアップデート

収集サイクルのこのフェーズは、ヒープをトラバースし、避難中に移動されたオブジェクトへの参照を更新するです。

参照の更新フェーズも、ほとんど同時に実行されます。 更新参照フェーズを初期化するinit-update-refsと、ルートセットを再更新し、コレクションセットからリージョンをリサイクルするfinal-update-refsの短い期間があります。 これらだけがストップザワールドモードを必要とします。

5. 他の実験的コレクターとの比較

最近Javaに導入された実験的なガベージコレクターはShenandoahだけではありません。 その他には、Zとイプシロンが含まれます。 それらがシェナンドアとどのように比較されるかを理解しましょう。

5.1. Zコレクター

Java 11で導入された、 Zコレクターは、非常に大きなヒープサイズ用に設計された単一世代の低レイテンシーコレクターです。 Zコレクターは、ほとんどの作業をユーザープログラムと同時に実行し、ヒープ参照の負荷バリアを活用します。

さらに、Zコレクターは、ポインターの色付けと呼ばれる手法で64ビットポインターを利用します。 ここで、色付きのポインタは、ヒープ上のオブジェクトに関する追加情報を格納します。 Zコレクターは、ポインターに格納されている追加情報を使用してオブジェクトを再マップし、メモリーの断片化を減らします。

大まかに言えば、Zコレクターの目標はShenandoahの目標と似ています。 どちらも、ヒープサイズに直接比例しない短い休止時間を達成することを目的としています。 ただし、ZコレクターよりもShenandoahで使用できるチューニングオプションが多くあります。

5.2. イプシロンコレクター

Epsilon もJava11で導入されており、ガベージコレクションに対するアプローチが大きく異なります。 これは基本的にパッシブまたは「ノーオペレーション」コレクターです。つまり、メモリ割り当てを処理しますが、リサイクルはしません。 したがって、ヒープがメモリを使い果たすと、JVMは単にシャットダウンします。

しかし、なぜ私たちはそのようなコレクターを使いたいのでしょうか? 基本的に、ガベージコレクターは、ユーザープログラムのパフォーマンスに間接的な影響を及ぼします。 アプリケーションのベンチマークを行い、ガベージコレクションがアプリケーションに与える影響を理解することは非常に困難です。

イプシロンはまさにその目的を果たします。 単にガベージコレクターの影響を取り除き、アプリケーションを分離して実行できるようにします。 ただし、これにより、アプリケーションのメモリ要件を非常に明確に理解できるようになります。 その結果、アプリケーションからより良いパフォーマンスを達成できます。

明らかに、Epsilonの目標はShenandoahの目標とは大きく異なります。

6. 結論

この記事では、Javaでのガベージコレクションの基本と、それを絶えず改善する必要性について説明しました。 Javaで導入された最新の実験コレクターであるShenandoahについて詳しく説明しました。 また、Javaで利用可能な他の実験的なコレクターとどのように競合するかについても説明しました。

ユニバーサルガベージコレクターの追求は、すぐには実現されません! したがって、G1はデフォルトのコレクターのままですが、これらの新しい追加により、低レイテンシーの状況でJavaを使用するためのオプションが提供されます。 ただし、これらを他の高スループットコレクターのドロップシッピング代替品と見なすべきではありません。