目次
1. 基本概要
Java プログラムの実行中、オブジェクトはヒープ メモリに動的に割り当てられます。プログラムが実行されると、一部のオブジェクトが参照されなくなり、ガベージになる場合があります。ガベージ コレクションとは、プログラムの実行中にこれらのガベージ オブジェクトをクリーンアップして、新しいオブジェクト用のメモリ領域を解放することを指します。
Java ガベージ コレクションの基本プロセスは、次の 3 つのステップに分けることができます。
- ガベージ分類: ガベージ コレクターはまず、どのオブジェクトがガベージ オブジェクトで、どのオブジェクトが生きたオブジェクトであるかを判断する必要があります。一般に、ガベージ コレクターは、ヒープ (プログラム カウンター、仮想マシン スタック、ローカル メソッド スタック、メソッド領域内のクラス静的属性など) のルート ノードから始まるオブジェクト グラフを走査し、到達可能なすべてのオブジェクトを次のようにマークします。生きているオブジェクト。マークされていないオブジェクトはガベージ オブジェクトとみなされます。
- ガベージ トレース: ガベージ コレクターは、クリーンアップするためにすべてのガベージ オブジェクトを見つける必要があります。ガベージ検索の方法が異なれば、ガベージ コレクション アルゴリズムも異なります。一般的なガベージ検索アルゴリズムには、マーク スイープ アルゴリズム、コピー アルゴリズム、マーク ソート アルゴリズム、世代別アルゴリズムなどが含まれます。
- ガベージ コレクション: ガベージ コレクターは、すべてのガベージ オブジェクトをクリーンアップする必要があります。ガベージ クリーニングの方法も異なります。一般的なものには、マーク スイープ アルゴリズム、コピー アルゴリズム、マーク ソート アルゴリズム、世代別アルゴリズムなどがあります。ガベージ クリーニングによりアプリケーションが一時停止する場合があります。ガベージ コレクタが異なれば、さまざまな方法でこの一時停止時間を短縮できるため、アプリケーションのパフォーマンスと信頼性が向上します。
ガベージ コレクションを実行する際、ガベージ コレクタが異なれば、異なるアルゴリズムや戦略が使用される可能性があることに注意してください。したがって、ガベージ コレクションの最適な効果を達成するには、さまざまなアプリケーション シナリオに対して、適切なガベージ コレクタを選択し、適切なパラメータ調整を実行する必要があります。
2. ごみの分別
基本的な背景
ガベージ分類とは、ヒープ内のオブジェクトをリビング オブジェクトとガベージ オブジェクトに分割するプロセスを指し、強参照、ソフト参照、弱参照、仮想参照などの参照型とは直接の関係はありません。
ガベージ分類フェーズでは、JVM はルート オブジェクトのセットから開始し、オブジェクト間の参照関係を通じてすべてのオブジェクトを横断し、生き残っているすべてのオブジェクトにマークを付けます。マーキング プロセス中に、オブジェクトはガベージ コレクションの後続の段階で処理するためにマークされます。マークされたオブジェクトは生きているオブジェクトですが、マークされていないオブジェクトはガベージ オブジェクトとみなされ、ガベージ コレクターによってリサイクルできます。
強参照、ソフト参照、弱参照、仮想参照などの参照タイプは、ガベージ コレクション プロセス中にオブジェクトのライフ サイクルを制御するために使用されます。これらの役割は、どのオブジェクトがリサイクル可能でどのオブジェクトがリサイクルできないかをガベージ コレクターに伝えることです。
さまざまな参照型の役割を説明する例を示します。
強力な参照
強参照は、最も一般的な参照型であり、デフォルトの参照型です。オブジェクトに強い参照がある場合、ガベージ コレクターはそれを再利用しません。メモリ領域が不十分な場合、JVM は強参照を持つオブジェクトを再利用するのではなく、OutOfMemoryError をスローします。強参照のコード例:
Object obj = new Object(); //强引用
ソフトリファレンス
ソフト参照は、有用ではあるが必須ではないオブジェクトを記述するために使用されます。ソフト参照に関連付けられたオブジェクトは、メモリが不足している場合にのみリサイクルされます。ソフト参照を使用すると、Web ページ キャッシュ、画像キャッシュなど、メモリに依存するキャッシュを実装できます。ソフト参照のコード例:
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj); //软引用
obj = null; //obj 不再具有强引用,但仍有软引用
弱参照
弱い参照は、必須ではないオブジェクトを記述するために使用されます。弱い参照に関連付けられたオブジェクトは、次のガベージ コレクションが発生するまでのみ存続できます。ガベージ コレクターが動作しているときは、現在のメモリが十分であるかどうかに関係なく、弱い参照のみに関連付けられたオブジェクトがリサイクルされます。弱参照のコード例:
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj); //弱引用
obj = null; //obj 不再具有强引用,只有弱引用
ファントムリファレンス
仮想参照は、ゴースト参照またはファントム参照とも呼ばれ、最も弱い参照タイプです。仮想参照を保持するオブジェクトは、参照を持たない場合と同様に、いつでもガベージ コレクターによってリサイクルされる可能性があります。仮想参照は主に、ガベージ コレクションされているオブジェクトのステータスを追跡するために使用されます。オブジェクトがリサイクルされようとしている場合、仮想参照は ReferenceQueue に配置され、ReferenceQueue を通じて通知を取得できます。仮想参照のコード例:
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue); //虚引用
obj = null; //obj 不再具有强引用,只有虚引用
つまり、さまざまな参照型を通じて、オブジェクトのライフ サイクルをより柔軟に制御し、ガベージ コレクターによるリサイクルが早すぎたり遅すぎたりすることを回避できます。
3. ゴミ箱検索
ジャンクな機会を見つける
ガベージ コレクターが異なれば戦略も異なります。以下は単なる例です。
- 新しいオブジェクト空間を申請してクラスをロードするときに、不十分な空間が適用されます。
- 古い世代と永続世代のスペース使用量が設定値に達しました (cms: CMSInitiatingOccupancyFraction=60、CMSInitiatingPermOccupancyFraction=60)
- System.gc() を呼び出す
スパム操作の検索
ガベージを見つけるには、参照カウントと到達可能性分析という 2 つの方法があります。
参照カウント方法: これは、単純なガベージ コレクション アルゴリズムです。その基本的な考え方は、オブジェクトに参照カウンタを追加することです。参照があるたびに、カウンタは 1 ずつ増加し、参照が期限切れになると、カウンタは 1 ずつ減らされます。 1. カウンタが 0 になると、オブジェクトは参照されなくなったとみなされ、リサイクルできるようになります。しかし、参照カウント方式では、オブジェクト間にリング構造が形成され、プログラムで使用されなくなってもカウンタが 0 にならない循環参照の問題は解決できません。
到達可能性分析手法: 最新のガベージ コレクション アルゴリズムの主要な実装手法です。その基本的な考え方は、「ルート オブジェクト」と呼ばれるオブジェクトのグループ (グローバル変数、スタック、メソッド領域など) から開始し、一連の参照関係を通じて、到達可能なオブジェクトは「生きている」とみなされ、到達できないオブジェクトはガベージとみなされます。そしてリサイクルする必要があります。オブジェクト間で形成された循環参照も、ルート オブジェクトとの間に参照チェーンがないため、到達可能性分析中に正しく処理されます。
4. ゴミの清掃
一般的に使用されるアルゴリズムの紹介
マークスイープ
GC は、マークとスイープの2 つのフェーズに分かれています。
まず、リサイクル可能なすべての物品にマークを付け、マーク付けが完了したら、マークが付いたすべての物品を一律にリサイクルします。
デメリットはクリア後に不連続な記憶の断片が発生すること。断片化が多すぎると、プログラムの実行中に大きなオブジェクトを割り当てる必要があるときに十分な連続メモリを見つけることができなくなり、GC を再度トリガーする必要があります。
マークコピー
メモリを容量に応じて 2 つのブロックに分割し、一度に 1 つのブロックのみを使用します。このメモリ ブロックが使い果たされると、残ったオブジェクトが別のブロックにコピーされ、使用されたメモリ領域はすぐにクリアされます。これにより、メモリの断片化の問題を考慮することなく、毎回メモリ領域の半分をリサイクルすることが可能になり、シンプルかつ効率的になります。
欠点は 2 倍のメモリ容量を必要とすることです。最適化方法の 1 つは、Eden 領域と Survivor 領域を使用することであり、具体的な手順は次のとおりです。
Eden 領域と Survivor 領域のメモリ空間比率はデフォルトで 8:1:1 であり、同時に使用されるのは eden 領域と Survivor 領域の 1 つだけです。マーキングが完了したら、生き残ったオブジェクトを別の未使用の存続領域にコピーします (一部の古いオブジェクトは古い世代にアップグレードされます)。
この方法では、通常の 2 スペース マーク コピー アルゴリズムと比較して、メモリ空間の 10% のみが無駄になります。その理由は、ほとんどの場合、若い gc の後に残っているオブジェクトはほとんどないからです。
マークコンパクト
マーキングと仕分けも 2 つの段階に分かれており、まずリサイクル可能なオブジェクトにマークを付け、次に残っているオブジェクトを端に移動し、境界の外側のメモリをクリーンアップします。
この方法は、マークスイープ アルゴリズムの断片化の問題を回避し、コピー アルゴリズムのスペースの問題も回避します。
一般に、若い世代で GC を実行すると、少数のオブジェクトが残り、コピー アルゴリズムが使用されるため、生き残ったオブジェクトのコピーにかかるコストが少なく、コレクションを完了できます。
旧世代では、オブジェクトの生存率が高いため、マーク付きコピーアルゴリズムを使用するとデータのコピー効率が低く、スペースが無駄になってしまいます。したがって、リサイクルにはマーク スイープ アルゴリズムまたはマーク コンパクト アルゴリズムを使用する必要があります。
したがって、通常は最初にマーク アンド クリア アルゴリズムを使用し、フラグメント化率が高い場合はマーク デフレーション アルゴリズムを使用できます。
世代別収集アルゴリズム
問題の背景
上記のことから、基本的なガベージ コレクション アルゴリズムはどれも特効薬ではなく、それぞれに異なる特性があり、すべてのシナリオに対応できるわけではありません。最新の JVM では、多数の実際のシナリオの分析を通じて、JVM メモリ内のオブジェクトが大きく 2 つのカテゴリに分類できることがわかりました。1 つはローカル変数、一時変数など、ライフ サイクルが非常に短いタイプのオブジェクトです。オブジェクトなど ユーザー アプリケーションの DB 長い接続の Connection オブジェクトなど、別の種類のオブジェクトは長期間存続します。
上の図では、縦軸は JVM メモリ使用量、横軸は時間です。この図から、ほとんどのオブジェクトのライフ サイクルは非常に短く、GC 後に存続できるオブジェクトはほとんどないことがわかります。これに基づいて、世代のアイデアが生まれました。JDK7 では、ホットスポット仮想マシンは主に、メモリを若い世代、古い世代、永続的な世代の 3 つの大きなブロックに分割します。
世代領域の説明
主な基本的な地域分類分析は次のとおりです。
新生代: 新生代は主にエデン地域とサバイバー地域の2つの部分に分かれており、サバイバー地域はS0とS1の2つの部分に分けることができます。この領域は旧世代に比べて容量が狭く、オブジェクトのライフサイクルが短く、GCが頻繁に発生します。したがって、この分野ではマーク付きレプリケーション アルゴリズムがよく使用されます。
旧世代: 旧世代の全体的なスペースは大きく、物のライフサイクルは長く、生存率は高く、リサイクルの頻度は高くありません。したがって、マークソートアルゴリズムにより適しています。
永続世代: 永続世代はメソッド領域とも呼ばれ、クラスとインターフェイスのメタ情報とインターンされた文字列情報が格納されます。JDK8ではメタスペースに置き換えられました。
メタスペース: JDK8 以降に導入され、メソッド領域もメタスペースに存在します。
世代別ガベージコレクションアルゴリズム実行プロセス
-
初期状態:オブジェクトはEden領域に配置されており、S0、S1領域はほぼ空です。
-
プログラムが実行されるにつれて、より多くのオブジェクトが Eden 領域に割り当てられます。
-
Eden が解放できない場合は、MinorGC (YoungGC) が発生しますが、このとき、まず到達不能なガベージオブジェクトが特定され、到達可能なオブジェクトが S0 領域に移動され、到達不能なオブジェクトがクリーンアップされます。現時点では、エデンエリアは空です。この処理では、マーククリーニングアルゴリズムとマークコピーアルゴリズムが使用されます。
-
Eden が解放できない場合は、再度、minorGC がトリガーされるので、前と同様に、最初にマークしてください。このとき、Eden エリアと S0 エリアにはゴミオブジェクトが存在する可能性がありますが、S1 エリアは空です。このとき、Eden エリアと S0 エリアのオブジェクトは直接 S1 エリアに移動され、Eden エリアと S0 エリアのゴミオブジェクトはクリーンアップされます。このラウンドのMinorGCの後、EdenおよびS0エリアは空になります。
-
プログラムを実行すると、Eden 領域が確保され、MinorGC 処理が繰り返されますが、このとき S0 領域は空いており、S0 領域と S1 領域が入れ替わります。エデンとS1エリアから転送され、S0エリアに移動します。その後、エデンと S1 エリアのゴミが除去され、このラウンドが完了すると、これら 2 つのエリアは空になります。
-
プログラムの実行中、ほとんどのオブジェクトはすぐに消滅しますが、長期間存続するオブジェクトもいくつかあり、これらのオブジェクトについては、S0 および S1 領域での繰り返しの移動により、一定のパフォーマンスのオーバーヘッドが発生し、プログラムの効率が低下します。 GC。したがって、オブジェクトのプロモーションの動作が導入されました。
-
オブジェクトが新世代のエデン、S0、S1 エリアの間にある場合、あるエリアから別のエリアに移動するたびに、オブジェクトの年齢が 1 つ増加します。一定のしきい値に達した後、オブジェクトがまだ生きている場合、オブジェクトの年齢は 1 つ増加します。オブジェクトは高齢者に昇格します。
-
旧世代も割り当てられている場合、MajorGC (フル GC) が発生し、旧世代はオブジェクト数が多いため、マーク補完アルゴリズムに時間がかかり、STW 現象が発生します。フル GC の原因を減らすか回避するために最善を尽くしてください。
参考文献、書籍、リンク
1. JVM クラシック ガベージ コレクターの動作メカニズムと原理 - Kang Zhixing のブログ | kangzhixing Blog
2.「Java仮想マシンを深く理解する」
3.「ガベージコレクションのアルゴリズムと実装」