Java ガベージ コレクション時間を 90% 削減するにはどうすればよいですか? Java での GC の最適化の実行方法については、以下で詳しく説明します。 JVM の GC メカニズムにより、開発者はメモリ管理の詳細から保護され、開発効率が向上します。 apache php mysql
少し前、私たちは Ali-HBase で一般的に認識されている問題点を克服する準備をしました。この目的のために、詳細な分析と包括的な革新作業を実施し、比較的良好な結果を達成しました。 Ant リスク制御シナリオを例にとると、HBase のオンライン ヤング GC 時間は 120 ミリ秒から 15 ミリ秒に短縮され、Alibaba JDK チームが提供するツールである ZenGC と組み合わせることで、実験室のストレス テスト環境ではさらに 5 ミリ秒に達しました。この記事では主に、この分野におけるこれまでの取り組みと技術的なアイデアの一部を紹介します。
JVM の GC メカニズムは、開発者をメモリ管理の詳細から保護し、開発効率を向上させます。 GC について言えば、多くの人は最初に、JVM が長時間停止するか、FGC によってプロセスがスタックして使用できなくなるのではないかと思われるでしょう。しかし、HBase のようなビッグ データ ストレージ サービスの場合、JVM によってもたらされる GC の課題は非常に複雑で困難です。理由は 3 つあります:
1. メモリ サイズが大きい。オンライン HBase プロセスのほとんどは 96G の大規模ヒープであり、今年の新モデルではすでに 160G を超えるヒープ構成がいくつかリリースされています
2 オブジェクトのステータスは複雑です。 HBase サーバーは、内部で多数の読み取りおよび書き込みキャッシュを維持しており、その規模は数十 GB に達します。 HBase は、順序付けされたサービス データをテーブル形式で提供します。これらのデータ構造では、1 億を超えるオブジェクトと参照が生成されます。アクセス圧力が大きくなると、若い領域でのメモリ消費が速くなり、一部のビジー クラスターでは 1 秒あたり 1 ~ 2 つの若い GC に達する可能性がありますが、若い領域が大きいと GC の停止が大きくなり、パフォーマンスが低下します。ビジネスの要件。
考え方
高速でより経済的な CCSMap
以下ではこれを書き込みキャッシュと呼びます。書き込みキャッシュはクエリ可能であり、メモリ内でデータを順序付けする必要があります。同時読み取りと書き込みの効率を向上させ、データの順序付けとシーク&スキャンのサポートという基本要件を達成するために、SkipList は広く使用されているデータ構造です。
JDK に付属する ConcurrentSkipListMap を分析の例として取り上げます。これには次の 3 つの問題があります。
現在の若い GC の最大の敵を分析した結果、書き込みキャッシュの割り当て、アクセス、破壊、リサイクルはすべて私たちが管理するため、JVM が書き込みキャッシュを「認識できない」場合、私たちは大胆なアイデアを思いつきました。ライト キャッシュのライフ サイクルを自分たちで管理すれば、GC 問題は自然に解決されます。
質問は、「JVM オブジェクトを使用せずに同時アクセスをサポートする順序付けされたマップを構築する方法」に変換されました。
もちろん、Map の書き込み速度は HBase の書き込みスループットに密接に関係しているため、パフォーマンスの低下を受け入れることはできません。
オブジェクトを使用せず、パフォーマンスを損なうことなく同時アクセスをサポートする順序付けされたマップを構築する方法という要求が再び強化されています。
この目標を達成するために、次のようなデータ構造を設計しました:
連続メモリ (ヒープ内またはヒープ外) を使用し、JVM のオブジェクト メカニズムに依存するのではなく、コードを通じて内部構造を制御します。
論理的には、これはロックフリーの同時書き込みとクエリをサポートする SkipList でもあり、コントロール ポインターとデータは連続メモリ構造に保存されます。 ライト キャッシュ メモリを大きなメモリ セグメント (チャンク) の形式で申請します。各チャンクには複数のノードが含まれており、各ノードは要素に対応します。新しく挿入された要素は常に、使用されているメモリの最後に配置されます。 Node内部の複雑な構造には、メンテナンス情報やIndex/Next/Key/Valueなどのデータが格納されています。新しく挿入された要素は、ノード構造にコピーする必要があります。 HBase で書き込みキャッシュ ダンプが発生すると、CCSMap 全体のすべてのチャンクがリサイクルされます。要素が削除されると、その要素はリンク リストから論理的に「キック」されるだけで、実際にメモリから要素を回復することはありません (もちろん、実際に回復する方法はありますが、HBase に関する限り、必要ありません)。
JVM オブジェクトがないため、各 JVM オブジェクトは少なくとも 16 バイトのスペースを占有し、保存できます (8 バイトはタグ用に予約され、8 バイトは型ポインターです)。 50 バイト長の KeyValue を例にとると、JDK に付属の ConcurrentSkipListMap と比較して、CCSMap のメモリ使用量は 40% 削減されます。
最適化前
最適化後 CCSMap の使用後、元の 1 億 2,000 万個の存続オブジェクトが数十ミリ秒以内に減少しました数百万レベルになると、GC 圧力が大幅に低下します。コンパクトなメモリ配置により、書き込みスループットも 30% 向上しました。 キャッシュ: BucketCacheHBase はディスク上のデータをブロックの形式で編成します。一般的な HBase ブロック サイズは 16K ~ 64K です。 HBase は、ディスク I/O を削減するために BlockCache を内部的に維持します。 BlockCache は、書き込みキャッシュと同様に、GC アルゴリズム理論の世代仮説に準拠しておらず、本質的に GC アルゴリズムにとって好ましくありません。一時的でも永続的でもありません。ブロック データの一部がディスクから JVM メモリにロードされ、そのライフ サイクルは数分から数か月に及び、ほとんどのブロックは古い領域に入り、メジャー GC 中にのみ JVM によってリサイクルされます。その問題点は主に次の点に反映されます:
HBase ブロックのサイズが固定されておらず、比較的大きく、メモリが断片化されやすい
ParNew アルゴリズムでは、プロモーションが面倒です。この問題はコピーのコストには反映されませんが、サイズが大きいことと、HBase ブロックを保存するための適切なスペースを見つけるのにコストがかかることが原因です。 読み取りキャッシュの最適化の考え方は、ブロックがメモリにロードされるときに、BlockCache として返されることのないメモリ部分を JVM に適用することです。 、ブロックをセグメント化された範囲にコピーし、使用済みとしてマークします。このブロックが不要になると、その間隔が使用可能としてマークされ、新しいブロックを再保存できるようになります。これが BucketCache です。 BucketCache でのメモリ空間の割り当てとリサイクルについて (この領域の設計と開発は何年も前に完了しています)BucketCache
オフヒープ メモリに基づく多くの RPC フレームワークは、オフヒープ メモリ自体の割り当てとリサイクルも管理します一般に、メモリは明示的な解放によって再利用されます。ただし、HBase にはいくつかの困難があります。 Block オブジェクトは、自己管理が必要なメモリ セグメントと考えられます。ブロックは複数のタスクによって参照される可能性があります。ブロックのリサイクルの問題を解決する最も簡単な方法は、ブロックをタスクごとにスタックにコピーし (コピーされたブロックは通常、古い領域にプロモートされません)、それを JVM に転送することです。管理。
実際、私たちは以前にもこの方法を使用していました。実装が簡単で、JVM が承認し、安全で信頼性があります。しかし、これは損失の多いメモリ管理方法であり、GC 問題を解決するために、リクエストごとにコピー コストが導入されます。スタックへのコピーには追加の CPU コピー コストと若い領域のメモリ割り当てコストが必要となるため、CPU とバスがますます貴重になっている現在、この価格は高く感じられます。
そこで、HBase で発生する主な問題点は、参照カウントを使用することにしました:
同じブロックを参照する複数のタスクが HBase 内に存在する可能性があります
同じタスク変数内に複数のタスクが存在する可能性があります。同じブロックを参照します。参照は、スタック上の一時変数またはヒープ上のオブジェクト フィールドである可能性があります。
Block の処理ロジックは、パラメーター、戻り値、フィールド割り当ての形式で複数の関数とオブジェクト間で受け渡されます。
ブロックは当社によって管理される場合もあれば、管理されない場合もあります(手動で解放する必要があるブロックもあれば、そうでないブロックもあります)。
ブロックはブロックのサブタイプに変換される場合があります。
これらの点を総合すると、正しいコードを書くのは困難です。しかし、C++ では、スマート ポインタを使用してオブジェクトのライフサイクルを管理するのが自然です。なぜ Java ではそれが難しいのでしょうか。
Java の変数代入は、ユーザー コード レベルでは参照代入動作のみを生成しますが、C++ の変数代入ではオブジェクトのコンストラクターとデストラクターを使用して多くのことを行うことができ、スマート ポインターはこれに基づいて実装されます (もちろん、 C++ のコンストラクターとデストラクターを不適切に使用すると、多くの問題が発生しますが、それぞれに独自の長所と短所がありますが、ここでは説明しません)
そこで、C++ のスマート ポインターを参照し、ブロック参照管理およびリサイクル フレームワーク ShrableHolder を設計しました。それ以外の場合はコーディングが困難です。これには次のパラダイムがあります:
ShrableHolder は、参照カウントされるオブジェクトと参照カウントされないオブジェクトを管理できます。
ShrableHolder は、再割り当てされるときに前のオブジェクトを解放します。管理対象オブジェクトの場合、参照カウントは 1 減算されますが、管理対象オブジェクトでない場合は、変更はありません。
ShrableHolderは、タスク終了時またはコードセグメント終了時にresetを呼び出す必要があります
ShrableHolderを直接割り当てることはできません。コンテンツを転送するには、ShrableHolder によって提供されるメソッドを呼び出す必要があります
ShrableHolder を直接割り当てることができないため、ライフサイクル セマンティクスを含むブロックを関数に渡す必要がある場合、ShrableHolder を関数のパラメーターとして使用することはできません。
このパラダイムに従って書かれたコードには、元のコードに対する論理的な変更はほとんどなく、if else は導入されていません。まだ多少の複雑さはあるようですが、幸いなことに、これによる影響を受ける範囲は依然として非常にローカルな下位層に限定されており、HBase ではまだ許容可能です。安全を期してメモリ リークを回避するために、長期間非アクティブな参照を検出する検出メカニズムをこのフレームワークに追加しました。検出されると強制的に削除対象としてマークされます。
BucketCache を適用すると、BlockCache のプロモーション オーバーヘッドが減少し、若い GC 時間が短縮されます:
(CCSMap+BucketCache 最適化の効果)
上記の後で最適化後、Ant Risk Control 運用環境の若い GC 時間は 15 ミリ秒に短縮されました。この規模で ParNew+CMS アルゴリズムを最適化することはすでに困難であるため、ZenGC に目を向けました。 ZenGC は G1 アルゴリズムに基づいて徹底的な改良を加え、HBase と ZenGC の自己管理メモリ ヒープが良い化学反応を生み出しました。
ZenGC は、G1 アルゴリズムに基づいて Alibaba JVM チームによって最適化され、ラージ ヒープ (LargeHeap) アプリケーション シナリオを対象とした GC アルゴリズムの総称です。ここでは主にマルチテナントGCについて紹介します。
マルチテナント GC には 3 つのコア ロジック層が含まれています。1) JavaHeap では、オブジェクトの割り当てがテナントに応じて分離され、異なるテナントが異なるヒープ領域を使用します。2) GC を、より低いコストでテナント単位で実行できるようにします。アプリケーション全体のみ。 3) 上位層アプリケーションがビジネス ニーズに応じてテナントを柔軟にマッピングできるようにします。
ZenGC はメモリ領域を複数のテナントに分割し、各テナントで独立して GC をトリガーします。これに基づいて、メモリを通常のテナントと中程度のライフサイクル テナントに分割します。中程度の寿命を持つオブジェクトは、一時的でも永続的でもないオブジェクトです。上記の 2 つの主要な最適化により、ライフサイクル オブジェクトの数とヒープ内のメモリ使用量は非常に少なくなりました。ただし、中程度のライフ サイクル オブジェクトは、生成時に古い領域オブジェクトによって参照されるため、若い GC ごとに RSet をスキャンする必要がありますが、これは依然として若い GC で最も時間がかかる部分です。
AJDK チームの ObjectTrace 関数の助けを借りて、中ライフ サイクル オブジェクトの「最大の」部分を見つけ出し、これらのオブジェクトが生成されたときに中ライフ サイクル テナントの古い領域に直接割り当てます。 、RSet マーキングを回避します。通常のテナントは通常の方法でメモリを割り当てます。
通常のテナントの GC 頻度は非常に高いですが、昇格されたオブジェクトや世代間参照がほとんどないため、ヤング ゾーンの GC 時間は適切に制御されています。実験室シーンのシミュレーション環境では、若い GC を 5 ミリ秒に最適化しました。
(ZenGC 最適化効果、ユニットの問題、ここにあります)
Ali-HBase は現在、Alibaba Cloud で商用サービスを提供しており、ニーズがある人は誰でもすべてのユーザーが利用できますAlibaba Cloud 上で大幅に改善されたワンストップの HBase サービスを使用します。クラウド HBase バージョンは、自社構築の HBase と比較して、運用と保守、信頼性、パフォーマンス、安定性、セキュリティ、コストの点で多くの点で改善されています。
関連記事:
Java ガベージ コレクションのオーバーヘッドを削減するための 5 つの提案
関連ビデオ:
ガベージ コレクションのメカニズム - Han Shuping 2016 最新の PHP オブジェクト指向プログラミング ビデオ チュートリアル
以上がJava のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。