Java パフォーマンスの決定版ガイド - 概要 5

ガベージ コレクションの概要

多くの場合、コードを書き直す機会がなく、Java アプリケーションのパフォーマンスを向上させる必要があるため、ガベージ コレクターのチューニングが重要になります。

最新の JVM にはさまざまな種類があり、最も主流のガベージ コレクターは、シリアル コレクター (シングル CPU 環境でよく使用される)、スループット (またはパラレル) コレクター、コンカレント コレクター (CMS)、および G1 コレクターの 4 つです。それらのパフォーマンス特性は大きく異なりますが、共通点も多くあります。

ガベージ コレクションの概要

Java の特徴の 1 つは、オブジェクトのライフ サイクルを明示的に管理する必要がないことです。オブジェクトは必要なときに作成でき、オブジェクトが使用されなくなった場合は、バックグラウンドで JVM によって自動的にリサイクルされます。これは実際には諸刃の剣であり、Java プログラムのメモリ使用量の最適化に多くの時間を費やしている場合、このメカニズムは利点というよりも欠点のように見えるかもしれません。

簡単に言うと、ガベージ コレクションは 2 つのステップで構成されます。使用されなくなったオブジェクトを見つけて、それらのオブジェクトによって管理されているメモリを解放します。JVM は、使用されなくなったオブジェクト (ガベージ オブジェクト) を見つけることから始めます。これは、オブジェクト参照を持たなくなったオブジェクトを見つけることと呼ばれることもあります (オブジェクト参照をカウントする「参照カウント」を意味します)。ただし、参照によってカウントするこの方法はあまり信頼できません。オブジェクトのリンクされたリストがあり、リスト内の各オブジェクト (ヘッド ノードを除く) がリスト内の別のオブジェクトを指しているとします。ただし、そのリストを指す参照が存在しない場合は、 head の場合、このリストは未使用なので、ガベージ コレクターによって再利用できます。これが循環リストの場合 (つまり、リストの末尾の要素が先頭の要素を指している)、リスト内のオブジェクトが実際に使用されていない場合でも、リスト内の各要素には参照が含まれます。オブジェクトはこのリストを指します。

したがって、参照をカウントすることによって動的に追跡することはできず、JVM は定期的にヒープをスキャンして、使用されなくなったオブジェクトを見つける必要があります。ガベージ オブジェクトが見つかると、JVM はこれらのオブジェクトが保持するメモリを再利用し、メモリを必要とする他のオブジェクトに割り当てます。ただし、単に空きメモリを記録しただけでは、将来的に十分な空きメモリがあるとは限りません。場合によっては、メモリの断片化を防ぐためにメモリのデフラグを実行する必要があります。

次のシナリオを想定します。プログラムは、サイズ 1000 バイトの配列を割り当て、次にサイズ 24 バイトの配列を割り当て、そのような割り当てをループで継続する必要があります。最終的にプログラムはヒープ全体を使い果たし、その結果は次の図に示されています。
ここに画像の説明を挿入
ヒープ メモリが使い果たされると、JVM が未使用の配列スペースを再利用するようになります。サイズ 24 バイトの配列がすべて使用されなくなり、サイズ 1000 バイトの配列が引き続き使用されると仮定すると、上図の 2 行目のシーンが形成されます。ヒープ内には十分な空き領域がありますが、JVM がサイズ 1000 バイトのすべての配列を移動して連続して格納し、空き領域をより大きな領域に統合しない限り、24 バイトを超える連続領域は見つかりません。メモリ割り当て (上の図の 3 行目など)。

これらの特定の実装について詳しく説明するのは些細なことのように思えますが、ガベージ コレクションのパフォーマンスは次の基本操作によって決まります。 ** 使用されなくなったオブジェクトを検索し、オブジェクトが使用しているメモリを再利用し、ヒープのメモリ レイアウトを圧縮します。** コレクターが異なれば、これらの操作を完了するために使用する方法も異なります。そのため、ガベージ コレクターが異なればパフォーマンス特性も異なります。

アプリケーション スレッドが実行されていない状態でガベージ コレクションが発生した場合、これらの操作は簡単に実行されます。ただし、実際の状況はさらに複雑で、通常、Java プログラムは多数のスレッドを開始し、ガベージ コレクター自体もマルチスレッドであることがよくあります。以下の説明では、スレッドは論理的に、アプリケーション スレッドとガベージ コレクションを処理するスレッドの 2 つのグループに分けられます。ガベージ コレクターがオブジェクトを再利用したり、メモリ内でオブジェクトを移動したりするときは、アプリケーション スレッドがこれらのオブジェクトを使用し続けないようにする必要があります。これは、コレクタがオブジェクトを移動する場合に特に重要です。操作中にオブジェクトのメモリ アドレスが変更されるため、このプロセス中はアプリケーション スレッドがオブジェクトにアクセスすべきではありません。

すべてのアプリケーション スレッドの実行が停止する一時停止は、stop-the-world 一時停止と呼ばれます。通常、これらの一時停止はアプリケーションのパフォーマンスに最も大きな影響を及ぼし、ガベージ コレクションを調整する際には、このような一時停止を最小限に抑えることが最も重要な考慮事項となります。

世代別ガベージ コレクター

実装の詳細は大きく異なりますが、すべてのガベージ コレクターは同じアプローチに従い、状況に応じてヒープを異なる世代 (Generation) に分割します。これらの世代は、「Old Generation」(Old Generation または TenuredGeneration)および「New Generation」(Young Generation)と呼ばれます。新世代はさらに、エデン スペースとサバイバー スペースと呼ばれる異なるセグメントに分割されます (ただし、エデンは新世代全体を指すために誤って使用されることがあります)。

生成メカニズムを使用する理由は、多くのオブジェクトの存続期間が非常に短いためです。以下の例を考えてみましょう。これは株価を計算し、株価と株価の平均価格の差を二乗し、その結果を (標準偏差計算の一部として) 合計するループです。

	sum = new BigDecimal(0);
	for (StockPrice sp: prices.values()) {
    
    
		BigDecimal diff = sp.getClosingPrice().subtract(averagePrice);
		diff = diff.multiply(diff);
		sum = sum.add(diff);
	}

BigDecimal多くの Java クラスと同様、これは不変オブジェクトです。オブジェクトは変更できない数値を表します。操作でこのオブジェクトを使用すると、新しいオブジェクトが作成されます (通常、前のオブジェクトとその値は破棄されます)。BigDecimalこの単純なループで 1 年分の株式データ (約 250 ループ) を処理すると、この 1 つのループだけで、ループの中間値を格納するオブジェクトが750 個作成されます。これらのオブジェクトは、ループの次のサイクルの開始時に破棄されます。および他のメソッドではadd()、JDK のライブラリ メソッドはBigDecimal(および他のクラス) のような中間オブジェクトをさらに作成します。最終的には、小さなコードで多数のオブジェクトが作成され、非常に迅速に破棄されます。

Java では、この種の操作は非常に一般的であるため、ガベージ コレクターは多数 (場合によってはほとんど) の一時オブジェクトを処理するように設計されています。これはジェネレーション デザインの本来の意図の 1 つでもあります。若い世代は、オブジェクトが最初に割り当てられるヒープの部分です。新しい世代がいっぱいになると、ガベージ コレクターはすべてのアプリケーション スレッドを一時停止し、新しい世代のスペースを再利用します。使用されなくなったオブジェクトはリサイクルされ、まだ使用されているオブジェクトは別の場所に移動されます。この操作はマイナー GC と呼ばれます。

この設計を使用すると、パフォーマンス上の利点が 2 つあります。まず、若い世代はヒープの一部にすぎないため、ヒープ全体を処理するよりも若い世代を処理する方が高速です。これは、アプリケーション スレッドの一時停止時間が短縮されることを意味します。これは、JVM がガベージ コレクションの前にヒープ全体がいっぱいになるまで待機しなくなるため、アプリケーション スレッドがより頻繁に停止することを意味します。ただし、現時点では、たとえ頻繁に発生するとしても、一時停止を短くすることには明らかな利点があります。

2 番目の利点は、若い世代にオブジェクトが割り当てられる方法に由来します。オブジェクトは Eden 空間に割り当てられます (新世代空間の大部分を占めます)。** ガベージ コレクション中に、新しい世代のスペースは空になり、Eden スペース内のオブジェクトは削除またはリサイクルされ、生き残ったすべてのオブジェクトは別の Survivor スペースに移動されるか、古い世代に移動されます。すべてのオブジェクトが削除されるため、ガベージ コレクション中に新しい世代領域が圧縮されることと同じになります。**すべてのガベージ コレクション アルゴリズムには、新しい世代のガベージ コレクションの際に「時空の一時停止」現象が発生します。

オブジェクトは常に古い世代に移動されており、最終的には古い世代がいっぱいになるため、JVM は古い世代で使用されなくなったオブジェクトを見つけてリサイクルする必要があります。これは、ガベージ コレクション アルゴリズムが最も異なる点です。シンプルなガベージ コレクション アルゴリズムは、すべてのアプリケーション スレッドを直接停止し、使用されなくなったオブジェクトを見つけてリサイクルし、ヒープ領域を整理します。フル GC として知られるこのプロセスは、通常、アプリケーション スレッドに長い一時停止を引き起こします。

一方、より複雑な計算では、アプリケーション スレッドの実行中に未使用のオブジェクトを見つけることもできます。これが、CMS および G1 コレクターがガベージ コレクションを実行する方法です。CMS および G1 コレクターは、アプリケーション スレッドを停止せずに未使用のオブジェクトを検索できるため、コンカレント ガベージ コレクターと呼ばれます。同時に、アプリケーションが停止する可能性を最小限に抑えるため、低一時停止 (低一時停止) コレクターとも呼ばれます (この名前は非常に不正確ですが、非一時停止コレクターと呼ばれることもあります)。コンカレント コレクターは、さまざまな方法を使用して旧世代領域を圧縮します。

CMS または G1 コレクターを使用すると、アプリケーションで発生する一時停止が少なくなります (そして短くなります)。その代償として、アプリケーションがより多くの CPU を消費することになります。CMS および G1 コレクションでは、長いフル GC 一時停止が発生する場合もあります (このような一時停止を回避することは、これらのチューニング アルゴリズムで考慮すべき重要な側面です)。

ガベージ コレクターを評価するときは、達成する必要がある全体的なパフォーマンス目標について考慮してください。すべての決定にはトレードオフがあります。アプリケーションに単一リクエストの応答時間に関する要件がある場合 (Java Enterprise Edition サーバーなど)、次の要素を考慮する必要があります。

  • 個々のリクエストは一時停止時間の影響を受けますが、フル GC の長い一時停止の影響をより大きく受けます。応答時間を可能な限り短縮することが目標の場合は、同時コレクターの使用を選択する方が適切です。
  • 平均応答時間が最大応答時間よりも重要な場合 (たとえば、応答時間の 90%)、通常はスループット コレクターを使用することで要件を満たすことができます。

長時間の停止時間を避けるために同時コレクターを使用すると、追加の CPU を消費するという代償も伴います。同様に、バッチ アプリケーション用のガベージ コレクターを選択する場合は、次のガイドラインに従うことができます。

  • CPU が十分に強力な場合、同時コレクターを使用してフル GC の一時停止を回避すると、タスクの実行が高速化される可能性があります。
  • CPU が制限されている場合、同時コレクターの追加の CPU 消費により、バッチ タスクの消費時間が長くなります。

簡単な概要

  1. すべての GC アルゴリズムは、ヒープを古い​​世代と新しい世代に分割します。
  2. すべての GC アルゴリズムは、新しい世代のオブジェクトをクリーンアップするときに「stop-the-world」ガベージ コレクション メソッドを使用します。これは通常、より高速に完了できる操作です。

おすすめ

転載: blog.csdn.net/weixin_42583701/article/details/131039248