Java プラットフォームのガベージ コレクション メカニズムにより、開発者の効率は大幅に向上しましたが、ガベージ コレクタの実装が不十分だと、アプリケーション リソースが過剰に消費される可能性があります。 Java 仮想マシンのパフォーマンス最適化シリーズの第 3 部では、Eva Andreasson が Java 初心者に Java プラットフォームのメモリ モデルとガベージ コレクション メカニズムを紹介します。彼女は、Java アプリケーションのパフォーマンスにおける主な問題が断片化 (ガベージ コレクションではない) である理由、および Java アプリケーションの断片化に対処する現在主要な (しかし最も革新的ではない) 方法が世代別ガベージ コレクションと圧縮である理由を説明します。
ガベージ コレクション (GC) の目的は、アクティブなオブジェクトによって参照されなくなった Java オブジェクトによって占有されているメモリを解放することです。これは、Java 仮想マシンの動的メモリ管理メカニズムの中核部分です。一般的なガベージ コレクション サイクルでは、まだ参照されている (したがって到達可能な) オブジェクトはすべて保持されますが、参照されなくなったオブジェクトは解放され、それらのオブジェクトが占有している領域は新しいオブジェクトに割り当てるために再利用されます。
ガベージ コレクションのメカニズムとさまざまなガベージ コレクション アルゴリズムを理解するには、まず Java プラットフォームのメモリ モデルについて知っておく必要があります。
ガベージ コレクションと Java プラットフォームのメモリ モデル
コマンドラインから Java プログラムを起動し、起動パラメータ -Xmx (例: java -Xmx:2g MyApp) を指定すると、指定されたサイズのメモリが Java プロセス (いわゆる Java ヒープ) に割り当てられます。 。この専用メモリ アドレス空間は、Java プログラム (場合によっては JVM) によって作成されたオブジェクトを格納するために使用されます。アプリケーションが実行され、新しいオブジェクトにメモリが継続的に割り当てられると、Java ヒープ (つまり、専用メモリ アドレス空間) が徐々にいっぱいになります。
最終的に Java ヒープがいっぱいになります。これは、メモリ割り当てスレッドが新しいオブジェクトにメモリを割り当てるのに十分な大きさの連続した領域を見つけることができないことを意味します。この時点で、JVM はガベージ コレクタに通知してガベージ コレクションを開始することを決定します。ガベージ コレクションは、プログラム内で System.gc() を呼び出してトリガーすることもできますが、System.gc() を使用してもガベージ コレクションが実行されることは保証されません。ガベージ コレクションの前に、ガベージ コレクション メカニズムはまずガベージ コレクションを実行しても安全かどうかを判断します。アプリケーションのすべてのアクティブなスレッドが安全な時点に達すると、ガベージ コレクションを開始できます。たとえば、オブジェクトにメモリが割り当てられているときにガベージ コレクションを実行することはできません。また、CPU 命令の最適化中にガベージ コレクションを実行することもできません。これは、コンテキストが失われ、最終結果が不正確になる可能性が高いためです。
ガベージ コレクターは、アクティブな参照を持つオブジェクトを再利用することはできません。これにより、Java 仮想マシンの仕様が壊れてしまいます。死んだオブジェクトは最終的に後続のガベージ コレクションによってリサイクルされるため、死んだオブジェクトをすぐにリサイクルする必要はありません。ガベージ コレクションを実装するにはさまざまな方法がありますが、上記 2 つの点はすべてのガベージ コレクション実装で同じです。ガベージ コレクションの本当の課題は、オブジェクトが生きているかどうかを識別する方法と、アプリケーションにできるだけ影響を与えずにメモリを再利用する方法です。したがって、ガベージ コレクタには次の 2 つの目標があります。
1. メモリ オーバーフローを回避するために、アプリケーションのメモリ割り当てのニーズを満たすために、参照されていないメモリを迅速に解放します。
2. メモリを再利用する際の、実行中のアプリケーションのパフォーマンス (遅延とスループット) への影響を最小限に抑えます。
2 種類のガベージ コレクション
このシリーズの最初の記事では、ガベージ コレクションの 2 つの方法、つまり参照カウントと追跡コレクションを紹介しました。次に、これら 2 つのアプローチをさらに詳しく調査し、実稼働環境で使用されるいくつかのトレース収集アルゴリズムを紹介します。
参照カウントコレクター
参照カウント コレクターは、各 Java オブジェクトを指す参照の数を記録し、オブジェクトを指す参照の数が 0 に達すると、そのオブジェクトをすぐにリサイクルできます。この即時性が参照カウント コレクターの主な利点であり、参照が指していないメモリを維持する際のオーバーヘッドはほとんどありませんが、各オブジェクトの最新の参照カウントを追跡するのはコストがかかります。
参照カウント コレクターの主な問題は、参照カウントの精度を確保する方法です。もう 1 つのよく知られた問題は、循環参照を処理する方法です。 2 つのオブジェクトが相互に参照し、他のライブ オブジェクトによって参照されていない場合、どちらのオブジェクトを指す参照の数も 0 ではないため、2 つのオブジェクトのメモリは決して再利用されません。循環参照構造のメモリのリサイクルには大規模な分析 (翻訳者注: Java ヒープ上のグローバル分析) が必要ですが、これによりアルゴリズムの複雑さが増し、アプリケーションに追加のオーバーヘッドが生じます。
トレースコレクター
トレース コレクターは、ライブ オブジェクトの既知の初期セットへの参照 (参照と参照の参照) を反復することによって、すべてのライブ オブジェクトを見つけることができるという前提に基づいています。アクティブ オブジェクト (ルート オブジェクトとも呼ばれる) の初期セットは、レジスタ、グローバル オブジェクト、およびスタック フレームを分析することで決定できます。オブジェクトの初期セットを決定した後、追跡コレクターはこれらのオブジェクトの参照関係に従い、参照によって示されるオブジェクトをアクティブ オブジェクトとして順番にマークし、既知のアクティブ オブジェクトのセットが拡大し続けるようにします。このプロセスは、参照されているすべてのオブジェクトがライブ オブジェクトとしてマークされ、マークされていないオブジェクトのメモリが再利用されるまで続きます。
追跡コレクターは、循環参照構造を処理できるという点で、主に参照カウント コレクターとは異なります。ほとんどのトレース コレクターは、マーキング フェーズ中に循環参照構造内の未参照オブジェクトを検出します。
トレース コレクターは動的言語で最も一般的に使用されるメモリ管理方法であり、現在 Java でも最も一般的な方法であり、実稼働環境でも長年検証されています。以下では、トレース収集を実装するためのいくつかのアルゴリズムから始めて、トレース コレクターを紹介します。
トレース収集アルゴリズム
コピー ガベージ コレクターとマーク スイープ ガベージ コレクターは新しいものではありませんが、現在でもトラッキング コレクションを実装するための 2 つの最も一般的なアルゴリズムです。
ガベージコレクターのコピー
従来のコピー ガベージ コレクターは、ヒープ内の 2 つのアドレス スペース (つまり、from スペースと to スペース) を使用し、ガベージ コレクションが実行されると、from スペース内のすべてのアクティブ オブジェクトが to スペースにコピーされます。は削除されます (翻訳者注: to スペースまたは古い世代にコピーした後) は、from スペース全体をリサイクルできます。スペースが再度割り当てられると、to スペースが最初に使用されます (翻訳者注: つまり、to スペースです)。前のラウンドのスペースが宇宙からの新しいラウンドとして使用されます。
このアルゴリズムの初期の実装では、from スペースと to スペースはその位置を継続的に変更していました。つまり、to スペースがいっぱいになってガベージ コレクションがトリガーされると、図 1 に示すように to スペースが from スペースになります。 。
図 1 従来のコピー ガベージ コレクションのシーケンス
最新のコピー アルゴリズムにより、ヒープ内の任意のアドレス スペースをスペースに対して、またはスペースから使用できるようになります。この方法では、互いの位置を交換する必要はなく、論理的に位置を変更するだけです。
コピーコレクターの利点は、to スペース内のコピーされたオブジェクトがコンパクトに配置され、断片化がまったくないことです。断片化は他のガベージ コレクターが直面する一般的な問題であり、後で説明する主要な問題でもあります。
コピーコレクターのデメリット
一般に、コピー コレクターはストップ ザ ワールドです。つまり、ガベージ コレクションが進行中である限り、アプリケーションは実行できません。この実装では、コピーする必要があるものが増えるほど、アプリケーションのパフォーマンスへの影響が大きくなります。これは、応答時間に敏感なアプリケーションにとっては不利な点です。コピー コレクターを使用する場合は、最悪のシナリオ (つまり、from スペース内のすべてのオブジェクトがアクティブ オブジェクトである) も考慮する必要があります。このとき、これらのアクティブ オブジェクトを移動するのに十分な大きさのスペースを準備する必要があります。スペースは、すべてのオブジェクトを from スペースにインストールするのに十分な大きさである必要があります。この制限のため、コピー アルゴリズムのメモリ使用率はわずかに不十分です (翻訳者注: 最悪の場合、to 領域は from 領域と同じサイズである必要があるため、使用率は 50% のみになります)。
マーククリアコレクター
エンタープライズの実稼働環境にデプロイされたほとんどの商用 JVM は、アプリケーションのパフォーマンスに対するガベージ コレクターの影響を再現しないため、マーク スイープ (またはマーク) コレクターを使用します。最も有名なマーク コレクターには、CMS、G1、GenPar、DeterministicGC などがあります。
マーク スイープ コレクターはオブジェクト参照を追跡し、フラグ ビットを使用して見つかった各オブジェクトをライブとしてマークします。このフラグは通常、ヒープ上のアドレスまたはアドレスのグループに対応します。たとえば、アクティブ ビットは、オブジェクト ヘッダー内のビット (翻訳者注: ビット)、ビット ベクトル、またはビットマップにすることができます。
マーキングが完了すると、クリーンアップ段階に入ります。クリーンアップ フェーズでは、通常、ヒープ (ライブとマークされたオブジェクトだけでなく、ヒープ全体) を再度走査して、マークされていない連続したメモリ アドレス空間 (マークされていないメモリは空きで再利用可能) を見つけます。その後、コレクタがそれらをフリー リストに編成します。ガベージ コレクタは複数のフリー リストを持つことができます (通常はメモリ ブロックのサイズに応じて分割されます)。一部の JVM (例: JRockit Real Time) コレクタは、アプリケーションのパフォーマンス分析とオブジェクト サイズの統計に基づいてフリー リストを動的に分割することもあります。
クリーンアップ フェーズの後、アプリケーションはメモリを再度割り当てることができます。空きリストから新しいオブジェクトにメモリを割り当てる場合、新しく割り当てられたメモリ ブロックは、新しいオブジェクトのサイズ、スレッドの平均オブジェクト サイズ、またはアプリケーションの TLAB サイズに適合する必要があります。新しいオブジェクトに対して適切なサイズのメモリ ブロックを見つけると、メモリの最適化と断片化の軽減に役立ちます。
マーク - コレクターの欠陥をクリアする
マーク フェーズの実行時間はヒープ内のアクティブなオブジェクトの数によって異なりますが、クリーンアップ フェーズの実行時間はヒープのサイズによって異なります。したがって、ヒープ設定が大きく、ヒープ内にアクティブなオブジェクトが多数ある状況では、マーク スイープ アルゴリズムには一定の一時停止時間がかかります。
メモリを大量に使用するアプリケーションの場合、さまざまなアプリケーション シナリオやニーズに合わせてガベージ コレクション パラメータを調整できます。多くの場合、この調整により、マーク/スイープ フェーズによってアプリケーションまたはサービス契約の SLA に生じるリスクが少なくとも延期されます (ここでの SLA とは、アプリケーションが達成する必要がある応答時間を指します)。ただし、チューニングは特定の負荷とメモリ割り当て率に対してのみ有効です。負荷の変更やアプリケーション自体の変更には再チューニングが必要です。
マークスイープコレクターの実装
マークスイープ ガベージ コレクションを実装するには、商業的に実証済みの方法が少なくとも 2 つあります。 1 つは並列ガベージ コレクションで、もう 1 つは同時 (またはほとんどの場合は同時) ガベージ コレクションです。
パラレルコレクター
並列コレクションとは、ガベージ コレクション スレッドによってリソースが並列的に使用されることを意味します。並列コレクションの商用実装のほとんどは、ガベージ コレクションが完了するまですべてのアプリケーション スレッドが一時停止されるストップ ザ ワールド コレクターです。そのため、ガベージ コレクターは、通常、スループット ベンチマークで高いパフォーマンスを発揮します。スペックjbb。アプリケーションにとってスループットが重要な場合は、並列ガベージ コレクターが適しています。
並列コレクション (特に運用環境) の主なコストは、コピー コレクターと同様に、ガベージ コレクション中にアプリケーション スレッドが適切に機能できないことです。したがって、並列コレクターの使用は、応答時間に敏感なアプリケーションに大きな影響を与えます。特に、ヒープ領域に複雑なアクティブ オブジェクト構造が多数存在する場合、追跡する必要があるオブジェクト参照が多数存在します。 (マーク スイープ コレクターがメモリを再利用するのにかかる時間は、ライブ オブジェクトのコレクションを追跡するのにかかる時間と、ヒープ全体を走査するのにかかる時間に依存することに注意してください。) 並列アプローチでは、アプリケーションは一定時間一時停止されます。ガベージ コレクション時間全体。
同時コレクター
同時ガベージ コレクターは、応答時間に敏感なアプリケーションに適しています。同時実行とは、ガベージ コレクション スレッドとアプリケーション スレッドが同時に実行されることを意味します。ガベージ コレクション スレッドはすべてのリソースを所有しているわけではないため、アクティブなオブジェクト コレクションを追跡し、アプリケーション メモリがオーバーフローする前にメモリを再利用するのに十分な時間を確保して、ガベージ コレクションをいつ開始するかを決定する必要があります。一方、ガベージ コレクションが時間内に完了しない場合、アプリケーションはメモリ オーバーフロー エラーをスローします。一方、ガベージ コレクションはアプリケーションのリソースを消費し、スループットに影響を与えるため、あまり時間がかかることは望ましくありません。このバランスを維持するにはスキルが必要であるため、ガベージ コレクションをいつ開始するか、いつガベージ コレクションの最適化を選択するかを決定する際にヒューリスティックが使用されます。
もう 1 つの困難は、クリーンアップ フェーズに入るためにマーク フェーズがいつ完了するかを知る必要があるなど、一部の操作 (完全かつ正確なヒープ スナップショットを必要とする操作) をいつ安全に実行できるかを判断することです。ワールドはすでに一時停止されているため、これは stop-the-world パラレル コレクターにとっては問題ではありません (翻訳者注: アプリケーション スレッドは一時停止され、ガベージ コレクション スレッドがリソースを独占します)。ただし、同時コレクタの場合、マーキング フェーズからクリーニング フェーズにすぐに切り替えるのは安全ではない可能性があります。ガベージ コレクターによって追跡およびマークされているメモリの一部をアプリケーション スレッドが変更すると、新しいマークのない参照が生成される可能性があります。一部の同時コレクションの実装では、これにより、アプリケーションが空きメモリを必要とするときに空きメモリを取得できず、アノテーションが繰り返されるループに長時間陥る可能性があります。
これまでの説明から、多くのガベージ コレクターとガベージ コレクション アルゴリズムがあり、それぞれが特定のアプリケーション タイプとさまざまな負荷に適していることがわかりました。アルゴリズムが異なるだけでなく、アルゴリズムの実装も異なります。したがって、ガベージ コレクターを指定する前に、アプリケーションのニーズとその特性を理解することが最善です。次に、Java プラットフォームのメモリ モデルの落とし穴をいくつか紹介します。ここでの落とし穴とは、Java プログラマが動的に変化する運用環境で陥りがちな、アプリケーションのパフォーマンスを低下させるいくつかの想定を指します。
チューニングがガベージ コレクションの代わりにならない理由
ほとんどの Java プログラマーは、Java プログラムを最適化するためのオプションが多数あることを知っています。いくつかのオプションの JVM、ガベージ コレクター、およびパフォーマンス チューニング パラメーターを使用すると、開発者は終わりのないパフォーマンス チューニングに多くの時間を費やすことができます。このため、一部の人々はガベージ コレクションが悪いものであり、ガベージ コレクションの発生頻度を下げるか持続時間を短くするように調整することが良い回避策であると結論付けるようになりましたが、これには危険が伴います。
特定のアプリケーション向けのチューニングを検討してください。ほとんどのチューニング パラメータ (メモリ割り当て率、オブジェクト サイズ、応答時間など) は、現在のテスト データ量に基づくアプリケーションのメモリ割り当て率 (またはその他のパラメータ) に基づいています。最終的には次の 2 つの結果につながる可能性があります。
1. テストで合格したユースケースが、本番環境では失敗します。
2. データ量の変更やアプリケーションの変更には再チューニングが必要です。
チューニングは反復的であり、特に同時ガベージ コレクターには多くのチューニングが必要になる場合があります (特に運用環境では)。アプリケーションのニーズを満たすにはヒューリスティックが必要です。最悪のシナリオに対応するために、調整の結果は非常に厳格な構成になる可能性があり、これは大量のリソースの無駄にもつながります。この調整アプローチは、奇妙な探求です。実際、特定の負荷に合わせてガベージ コレクターを最適化すればするほど、Java ランタイムの動的な性質から遠ざかってしまいます。結局のところ、負荷が安定しているアプリケーションはいくつありますか?また、負荷の信頼性はどの程度であると期待できますか?
では、チューニングに重点を置いていない場合、メモリ不足エラーを防ぎ、応答時間を改善するにはどうすればよいでしょうか?まず最初に、Java アプリケーションのパフォーマンスに影響を与える主な要因を見つけることです。
断片化
Java アプリケーションのパフォーマンスに影響を与える要因は、ガベージ コレクターではなく、断片化と、ガベージ コレクターが断片化を処理する方法です。いわゆる断片化とは、ヒープ領域に空き領域はあるものの、新しいオブジェクトにメモリを割り当てるのに十分な大きさの連続したメモリ領域がない状態です。最初の記事で述べたように、メモリの断片化は、ヒープ内に残っている領域の TLAB、または存続期間の長いオブジェクトの間で解放された小さなオブジェクトによって占有されている領域のいずれかです。
時間が経つにつれて、アプリケーションが実行されるにつれて、この断片化はヒープ全体に広がります。場合によっては、静的に調整されたパラメーターを使用すると、アプリケーションの動的なニーズを満たせないため、状況が悪化する可能性があります。アプリケーションは、この断片化されたスペースを効率的に利用できません。何もしなければ、ガベージ コレクションが連続して実行され、ガベージ コレクターは新しいオブジェクトに割り当てるためにメモリを解放しようとします。最悪の場合、ガベージ コレクションを連続して実行してもメモリをさらに解放できず (断片化が多すぎる)、JVM はメモリ オーバーフロー エラーをスローする必要があります。断片化を解決するには、アプリケーションを再起動して、Java ヒープに新しいオブジェクトを割り当てるための連続したメモリ領域を確保します。プログラムを再起動するとダウンタイムが発生し、しばらくすると Java ヒープが再びフラグメントでいっぱいになり、再度再起動が必要になります。
プロセスをハングさせるメモリ不足エラーと、ガベージ コレクタが過負荷であることを示すログは、ガベージ コレクションがメモリを解放しようとしていること、およびヒープが大幅に断片化されていることを示します。プログラマーの中には、ガベージ コレクターを再度最適化することで断片化の問題を解決しようとする人もいます。しかし、私たちはこの問題を解決するためのもっと革新的な方法を見つける必要があると思います。次のセクションでは、断片化に対する 2 つの解決策、世代別ガベージ コレクションと圧縮に焦点を当てます。
世代別ガベージコレクション
実稼働環境のほとんどのオブジェクトは存続期間が短いという理論を聞いたことがあるかもしれません。世代別ガベージ コレクションは、この理論から派生したガベージ コレクション戦略です。世代別ガベージ コレクションでは、ヒープをさまざまなスペース (または世代) に分割し、それぞれのスペースにさまざまな年齢のオブジェクトを格納します。オブジェクトのいわゆる年齢は、オブジェクトが生き残ったガベージ コレクション サイクルの数です。オブジェクトがどれくらい古いか)ガベージ コレクション サイクル後も参照されます。
新しい世代に割り当てるスペースが残っていない場合、新しい世代のアクティブなオブジェクトは古い世代に移動されます (通常、世代は 2 つだけです。翻訳者注: 特定の年齢を満たすオブジェクトのみが古い世代に移動されます)。世代 ガベージ コレクションでは、多くの場合、一方向のコピー コレクターが使用されます。もちろん、一部の最新の JVM では、新世代と旧世代で異なるガベージ コレクション アルゴリズムを実装できます。パラレル コレクターまたはコピー コレクターを使用する場合、若いコレクターはストップ ザ ワールド コレクターになります (前の説明を参照)。
古い世代は、新しい世代から移動されたオブジェクトに割り当てられます。これらのオブジェクトは、長期間参照されているか、新しい世代のオブジェクトのコレクションによって参照されています。大きなオブジェクトの移動コストが比較的高いため、大きなオブジェクトが古い世代に直接割り当てられる場合があります。
世代別ガベージコレクション技術
世代別ガベージ コレクションでは、古い世代ではガベージ コレクションの実行頻度が低くなり、新しい世代ではより頻繁にガベージ コレクションが実行されます。また、新しい世代のガベージ コレクション サイクルが短くなることが期待されます。まれに、若い世代が古い世代よりも頻繁に収集される場合があります。これは、若い世代を大きくしすぎて、アプリケーション内のほとんどのオブジェクトが長期間存続する場合に発生する可能性があります。この場合、古い世代の設定が小さすぎて存続期間の長いオブジェクトをすべて収容できない場合、古い世代のガベージ コレクションも、移動されるオブジェクト用のスペースを解放するのに苦労します。ただし、一般的に言えば、世代別ガベージ コレクションにより、アプリケーションのパフォーマンスが向上します。
新しい世代を分割することのもう 1 つの利点は、断片化の問題がある程度解決されるか、最悪のシナリオが延期されることです。生存時間が短いこれらの小さなオブジェクトは断片化の問題を引き起こした可能性がありますが、それらはすべて新世代のガベージ コレクションでクリーンアップされます。存続期間の長いオブジェクトは古い世代に移動されるときに、よりコンパクトなスペースが割り当てられるため、古い世代もよりコンパクトになります。時間が経つと (アプリケーションが十分に長く実行される場合)、古い世代も断片化し、1 つ以上のフル ガベージ コレクションを実行する必要があり、JVM がメモリ不足エラーをスローすることもあります。しかし、新世代を切り開くことで最悪のシナリオが延期されるため、多くのアプリケーションにとってはそれで十分です。ほとんどのアプリケーションでは、stop-the-world ガベージ コレクションの頻度とメモリ不足エラーの可能性が減少します。
世代別ガベージコレクションを最適化する
前述したように、世代別ガベージ コレクションを使用すると、若い世代のサイズや昇格率などの調整など、繰り返しのチューニング作業が必要になります。特定のアプリケーション ランタイムのトレードオフを強調することはできません。固定サイズを選択するとアプリケーションが最適化されますが、避けられない動的な変更に対処するガベージ コレクターの能力も低下します。
新世代の第一原則は、ストップ・ザ・ワールド・ガベージ・コレクション時の遅延時間を確保しながら、遅延時間をできる限り大きくすると同時に、長期存続するオブジェクトのためにヒープ内に十分な領域を確保することです。世代別ガベージ コレクターを調整する際に考慮すべき追加の要素を次に示します。
1. 新しい世代のほとんどは stop-the-world ガベージ コレクターです。新しい世代の設定が大きくなるほど、対応する一時停止時間が長くなります。したがって、ガベージ コレクションの一時停止時間に大きな影響を受けるアプリケーションの場合は、若い世代の大きさを慎重に検討してください。
2. 世代ごとに異なるガベージ コレクション アルゴリズムを使用できます。たとえば、若い世代では並列ガベージ コレクションが使用され、古い世代では同時ガベージ コレクションが使用されます。
3. 頻繁な昇格 (訳者注: 新世代から旧世代への移行) が失敗する場合は、旧世代のフラグメントが多すぎる、つまり旧世代に十分なスペースがないことを意味します。新しい世代から移動されたオブジェクトを保存します。この時点で、プロモーション レートを調整したり (つまり、プロモーション期間を調整したり)、古い世代のガベージ コレクション アルゴリズムが圧縮を実行していることを確認して (次の段落で説明します)、アプリケーションの負荷に合わせて圧縮を調整したりできます。 。ヒープ サイズと各世代のサイズを増やすこともできますが、古い世代での一時停止時間がさらに長くなります。断片化は避けられないことを知ってください。
4. 世代別ガベージ コレクションは、存続時間が短い小さなオブジェクトが多く、ガベージ コレクション サイクルの最初のラウンドでリサイクルされます。このようなアプリケーションの場合、世代別ガベージ コレクションは断片化を効果的に削減し、断片化の影響を遅らせることができます。
圧縮
世代別ガベージ コレクションは断片化やメモリ不足エラーの発生を遅らせますが、圧縮は断片化問題に対する唯一の本当の解決策です。コンパクションは、オブジェクトを移動してメモリの連続ブロックを解放し、新しいオブジェクトを作成するのに十分なスペースを解放するガベージ コレクション戦略です。
オブジェクトの移動とオブジェクト参照の更新は、一定量の消費を伴うストップザワールド操作です (例外が 1 つあります。これについては、このシリーズの次の記事で説明します)。生き残るオブジェクトが多いほど、圧縮による一時停止時間が長くなります。残りのスペースがほとんどなく、断片化が激しい状況では (通常、プログラムが長時間実行されているため)、多くのライブオブジェクトを含む領域を圧縮する際に数秒の一時停止が発生する場合があります。また、メモリ オーバーフローに近づくと、ヒープ全体に数十秒かかる場合もあります。
圧縮の一時停止時間は、移動する必要があるメモリの量と更新する必要がある参照の数によって異なります。統計分析によると、ヒープが大きくなるほど、移動や参照の更新が必要なライブ オブジェクトの数も多くなります。一時停止時間は、1 GB ~ 2 GB のライブ オブジェクトが移動されるたびに約 1 秒かかります。4 GB サイズのヒープの場合、25% のライブ オブジェクトが存在する可能性が高いため、約 1 秒の一時停止が発生することがあります。
圧縮とアプリケーションのメモリウォール
アプリケーションのメモリ ウォールとは、ガベージ コレクション (コンパクションなど) によって発生する一時停止の前に設定できるヒープ サイズを指します。システムとアプリケーションに応じて、ほとんどの Java アプリケーションのメモリ ウォールは 4GB から 20GB の範囲になります。このため、ほとんどのエンタープライズ アプリケーションは、少数の大きな JVM ではなく、複数の小さな JVM にデプロイされます。これについて考えてみましょう。JVM の圧縮制限によって定義されている最新のエンタープライズ Java アプリケーションの設計とデプロイメントはどれだけあるでしょうか。この場合、ヒープの最適化の一時停止時間を回避するために、管理コストがより高いマルチインスタンス デプロイメントを採用することにしました。今日のハードウェアの大容量ストレージ機能と、エンタープライズクラスの Java アプリケーション用のメモリの増加の必要性を考慮すると、これは少し奇妙です。各インスタンスに数 GB のメモリしか設定されていないのはなぜですか。同時圧縮はメモリの壁を打ち破ります。これは私の次の記事のトピックです。
要約する
この記事は、ガベージ コレクションの概念とメカニズムを理解するのに役立つガベージ コレクションに関する入門記事であり、関連記事をさらに読む動機になることを願っています。ここで説明した内容の多くは長い間存在しており、いくつかの新しい概念は次の記事で紹介されます。たとえば、同時圧縮は現在、Azul の Zing JVM によって実装されています。これは、特にメモリと処理能力が今日向上し続けている中で、Java メモリ モデルの再定義を試みる新興のガベージ コレクション テクノロジです。
ガベージ コレクションに関する重要なポイントをいくつかまとめました。
1. さまざまなガベージ コレクション アルゴリズムと実装が、さまざまなアプリケーションのニーズに適応します。トラッキング ガベージ コレクターは、商用 Java 仮想マシンで最も一般的に使用されるガベージ コレクターです。
2. 並列ガベージ コレクションは、ガベージ コレクションの実行時にすべてのリソースを並列に使用します。これは通常、stop-the-world ガベージ コレクターであるため、スループットが高くなりますが、アプリケーションのワーカー スレッドはガベージ コレクション スレッドが完了するまで待機する必要があるため、アプリケーションの応答時間に一定の影響を与えます。
3. 同時ガベージ コレクション: コレクションの実行中、アプリケーション ワーカー スレッドは引き続き実行されます。同時ガベージ コレクターは、アプリケーションがメモリを必要とする前にガベージ コレクションを完了する必要があります。
4. 世代別ガベージ コレクションは断片化を遅らせるのに役立ちますが、断片化を排除することはできません。世代別ガベージ コレクションは、ヒープを 2 つのスペース (新しいオブジェクト用のスペースと古いオブジェクト用のスペース) に分割します。世代別ガベージ コレクションは、有効期間が短い小さなオブジェクトが多数あるアプリケーションに適しています。
5. 断片化を解決するには圧縮が唯一の方法です。ほとんどのガベージ コレクターは、ストップ ザ ワールド方式で圧縮を実行します。プログラムの実行時間が長くなるほど、オブジェクト参照が複雑になり、オブジェクト サイズが不均等に分散されるため、圧縮時間が長くなります。更新が必要なライブ オブジェクトや参照がさらに存在する可能性があるため、ヒープのサイズも圧縮時間に影響します。
6. チューニングにより、メモリ オーバーフロー エラーを遅らせることができます。しかし、過剰なチューニングの結果、構成が硬直化してしまいます。試行錯誤のアプローチでチューニングを開始する前に、運用環境の負荷、アプリケーションのオブジェクト タイプ、オブジェクト参照の特性を必ず理解してください。構成が硬すぎると動的負荷を処理できない可能性があるため、非動的値を設定する場合はその結果を必ず理解してください。
このシリーズの次の記事は、C4 (Concurrent Continuously Compacting Collector) ガベージ コレクション アルゴリズムについての詳細な説明です。ぜひご注目ください。
(全文終わり)