Javaにおけるガーベジコレクション
Javaのガーベジコレクションは、先進的なトピックの一つです。JavaのGCに関する知識は、私たちのアプリケーションの実行時パフォーマンスを最適化するのに役立ちます。
Javaにおけるガーベジコレクション
- Javaでは、プログラマーは使用されなくなったオブジェクトを破棄することに気を使う必要がありません。ガーベジコレクターがそれを処理します。
- ガベージコレクターはバックグラウンドで動作し続けるデーモンスレッドです。基本的に、到達不可能なオブジェクトを破棄することによってヒープメモリを解放します。
- 到達不能なオブジェクトは、プログラムのどの部分からも参照されなくなったオブジェクトです。
- 私たちはJVMオプションを通じてJavaプログラムのガーベジコレクタを選択できます。このチュートリアルの後のセクションでこれについて詳しく見ていきます。
自動ガベージコレクションはどのように機能しますか?
自動ガベージコレクションは、ヒープメモリを調べ、到達不可能なオブジェクトを特定(「マーキング」とも呼ばれる)し、それらを圧縮して破壊するプロセスです。このアプローチの問題は、オブジェクトの数が増えるにつれて、ガベージコレクションの時間が増加し続けることです。これは、到達不可能なオブジェクトを探すために、すべてのオブジェクトのリストを通過する必要があるからです。しかし、アプリケーションの経験的な分析によれば、ほとんどのオブジェクトは短命です。この行動はJVMの性能を向上させるために利用され、採用された方法論は一般に世代別ガベージコレクションと呼ばれます。この方法では、ヒープ領域は若世代、古世代または永続世代、恒久世代のように世代に分割されています。若世代のヒープ空間は、新しいオブジェクトがすべて作成される場所です。一度それが満杯になると、小規模なガベージコレクション(マイナーGCとも呼ばれる)が行われます。 つまり、この世代のすべての死んだオブジェクトは破棄されます。このプロセスは迅速であり、グラフからもわかるように、ほとんどが死んでいるでしょう。若い世代の生き残ったオブジェクトは年を取り、最終的には古い世代に移動します。古い世代は、長生きするオブジェクトを保存するために使用されます。通常、若い世代のオブジェクトにはしきい値が設定され、その年齢に達するとオブジェクトは古い世代に移動します。最終的に、古い世代は収集する必要があります。このイベントはメジャーGC(メジャーガーベジコレクション)と呼ばれ、すべての生きたオブジェクトが関与するため、しばしばはるかに遅くなります。また、完全GC(フルGC)もあり、これは全ヒープ—若い世代と古い世代の両方のスペースをクリーンアップすることを意味します。最後に、Java 7までには永続世代(またはパームジェン)があり、これはアプリケーションで使用されるクラスやメソッドを説明するためにJVMが必要とするメタデータを含んでいましたが、Java 8で削除されました。
Javaガベージコレクター
JVMは実際に4つの異なるガベージコレクタを提供しており、すべて世代型です。それぞれに利点と欠点があります。どのガベージコレクタを使用するかの選択は私たちに委ねられており、スループットやアプリケーションの停止時間に劇的な違いが生じる可能性があります。これらすべては、管理されたヒープを異なるセグメントに分割し、ヒープ内のほとんどのオブジェクトが短命であり、迅速に再利用されるべきであるという古くからの前提に基づいています。したがって、ガベージコレクタには4つの種類があります:
シリアルGC
これは最もシンプルなガーベジコレクタで、シングルスレッドシステムと小さなヒープサイズ用に設計されています。動作中はすべてのアプリケーションが停止します。-XX:+UseSerialGC JVMオプションを使用してオンにすることができます。
パラレル/スループットGC
これはJDK 8のJVMのデフォルトコレクタです。名前が示すように、ヒープスペースをスキャンし、コンパクションを行うために複数のスレッドを使用します。このコレクタの欠点は、マイナーGCまたはフルGCを実行している間にアプリケーションスレッドを一時停止することです。このコレクタは、こうした一時停止に耐えられるアプリケーションに最適であり、コレクタによって引き起こされるCPUオーバーヘッドを最適化しようとします。
CMSコレクター
CMSコレクタ(「同時マークスイープ」)アルゴリズムは、複数のスレッド(「同時」)を使用してヒープ(「マーク」)をスキャンし、再利用可能な未使用オブジェクトを見つけます(「スイープ」)。このコレクタは、次の2つのケースでストップ・ザ・ワールド(STW)モードに入ります:-ルートの初期マークを初期化している間、すなわちスレッドエントリポイントまたは静的変数から到達可能な古い世代のオブジェクト - アルゴリズムが並行して実行されている間にアプリケーションがヒープの状態を変更した場合に、正しいオブジェクトにマークを付けるために最後の手直しを行うために戻ることを強制されます。 このコレクターは昇進の失敗に直面する可能性があります。若い世代から古い世代にいくつかのオブジェクトを移動させる必要がある場合、コレクターが古い世代のスペースに十分な空きが作るための時間がなかった場合、昇進の失敗が発生します。これを防ぐために、古い世代にヒープサイズを増やしたり、コレクターに背景スレッドを増やすことを検討することがあります。
G1コレクター
最後に、ガーベジファーストコレクタがあり、これはヒープサイズが4GBを超えるように設計されています。ヒープサイズに基づき、1MBから32MBまでの領域にヒープサイズを分割します。ヒープ全体でオブジェクトの生存性を判断するために、同時にグローバルマーキングフェーズがあります。マーキングフェーズが完了すると、G1はほとんど空いている領域を把握します。そして、これらの領域から到達不能なオブジェクトを最初に収集するため、通常、多くの空きスペースが得られます。このため、G1はこれらの領域(ゴミを含む)を最初に収集し、ガーベジファーストという名前が付けられました。G1は、ユーザー定義の停止時間の目標を達成するために、停止予測モデルも使用しています。これは指定された停止時間の目標に基づいて収集する領域の数を選択します。G1のガーベジコレクションサイクルには、図に示されたフェーズが含まれます。
若年層専用フェーズ:このフェーズは若い世代のオブジェクトのみを含み、それらを老世代に昇格させます。若年層専用フェーズと空間再取得フェーズの間の移行は、老世代が特定のしきい値、すなわち開始ヒープ占有しきい値に達したときに始まります。この時、G1は通常の若年層専用コレクションの代わりに初期マーク若年層専用コレクションをスケジュールします。
初期マーク: このタイプのコレクションは、通常の若いオブジェクトのみのコレクションに加えてマークプロセスを開始します。同時マークは、古い世代領域内のすべての現在生存しているオブジェクトが、次のスペース回収フェーズのために保持されることを決定します。マークが完全に終了していない間は、通常の若いオブジェクトのみのコレクションが発生する場合があります。マークは、特別な2つのストップ・ザ・ワールドのポーズで終了します: リマークとクリーンアップです。
注:このポーズはマーク自体を完了させ、グローバル参照処理とクラスのアンロードを実行します。RemarkとCleanupの間に、G1は並行して生存情報の要約を計算します。これはCleanupポーズで最終的に確定され、内部データ構造を更新するために使用されます。
クリーンアップ: この一時停止は、完全に空の領域も含み、実際にスペース再獲得フェーズが続くかどうかを判断します。もしスペース再獲得フェーズが続く場合、若者専用フェーズは単一の若者専用コレクションで完了します。
スペース回収フェーズ:このフェーズは複数の混合コレクションで構成されており、若い世代の領域に加えて、古い世代の領域から生存しているオブジェクトも排出します。スペース回収フェーズは、G1が古い世代の領域をさらに排出しても、その労力に見合うだけの自由な空間が得られないと判断した時点で終了します。
G1は、–XX:+UseG1GCフラグを使用して有効にできます。この戦略により、バックグラウンドスレッドが到達不能なオブジェクトのスキャンを終える前にヒープが枯渇する可能性が減少しました。また、ヒープを「走行中」に圧縮することができ、これに対してCMSコレクタはSTWモードでのみ行うことができます。Java 8では、G1コレクタに、文字列重複除外と呼ばれる美しい最適化が提供されました。我々の文字列を表す文字配列は、ヒープの多くのスペースを占めていることがわかっています。G1コレクタが、ヒープ全体で重複している文字列を特定し、それらを同じ内部char[]配列を指すように変更できる新しい最適化が行われ、同じ文字列がヒープ内に不要に複数コピーされることを避けることができます。この最適化を有効にするために、-XX:+UseStringDeduplication JVM引数を使用できます。G1はJDK 9のデフォルトのガベージコレクタです。
Java 8のPermGenとMetaspace
前述の通り、Java 8以降、パーマネント・ジェネレーション領域は削除されました。したがって、JDK 8のHotSpot JVMは、メタデータの表現にネイティブメモリを使用します。これをメタスペースと呼びます。クラスメタデータの割り当てのほとんどはネイティブメモリから行われています。また、クラスメタデータに使用されるメモリの量を制限するための新しいフラグ、MaxMetaspaceSizeがあります。これに値を指定しない場合、メタスペースは実行中のアプリケーションの需要に応じて実行時にサイズが変更されます。クラスメタデータの使用がMaxMetaspaceSizeの制限に達すると、メタスペースのガベージコレクションがトリガーされます。過剰なメタスペースのガベージコレクションは、クラスやクラスローダーのメモリリーク、またはアプリケーションのサイズ設定が不十分であることの兆候かもしれません。これでJavaのガベージコレクションについては以上です。Javaにおけるさまざまなガベージコレクタについての理解が得られたことを願っています。参考文献:Oracleドキュメント、G1 GC。

