ホームページ >Java >&#&チュートリアル >Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

php是最好的语言
php是最好的语言オリジナル
2018-07-28 16:55:512302ブラウズ

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 の停止が大きくなり、パフォーマンスが低下します。ビジネスの要件。

考え方

    HBase は、ストレージ システムとして、書き込みバッファーと読み取りキャッシュとして大量のメモリを使用します。たとえば、96G (若い 4G + 古い 92G) の大きなヒープでは、書き込みバッファー + 読み取りキャッシュが占有します。メモリの 70% (約 70G) を超えると、ヒープ自体のメモリ レベルは 85% に制御され、残りの占有メモリは 10G 以内になります。したがって、この 70G 以上のメモリをアプリケーション レベルで自己管理できれば、JVM の場合、100G の大きなヒープの GC プレッシャーは 10G の小さなヒープの GC プレッシャーと同等になり、さらに大きなヒープにも直面することになります。将来的には膨満感が悪化することはありません。 このソリューションでは、オンラインの若い GC 時間が 120 ミリ秒から 15 ミリ秒に最適化されました。
  1. 高スループットのデータ集約型サービス システムでは、多数の一時オブジェクトが頻繁に作成され、リサイクルされます。これらの一時オブジェクトの割り当てとリサイクルを目標を絞った方法で管理する方法を、AliJDK チームが開発しました。テナントベースの GC アルゴリズム - ZenGC。グループの HBase は、この新しい ZenGC アルゴリズムに基づいて変換され、実験室で測定された若い GC 時間は 15 ミリ秒から 5 ミリ秒に短縮されました。これは予想外の極端な効果です。
  2. 以下では、Ali-HBase バージョンの GC 最適化に使用される主要なテクノロジーを 1 つずつ紹介します。

高速でより経済的な CCSMap

HBase で現在使用されているストレージ モデルは LSMTree モデルです。書き込まれたデータは一時的にメモリに一定のサイズで保存され、その後ディスクにダンプされてファイルが形成されます。

以下ではこれを書き込みキャッシュと呼びます。書き込みキャッシュはクエリ可能であり、メモリ内でデータを順序付けする必要があります。同時読み取りと書き込みの効率を向上させ、データの順序付けとシーク&スキャンのサポートという基本要件を達成するために、SkipList は広く使用されているデータ構造です。

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。 JDK に付属する ConcurrentSkipListMap を分析の例として取り上げます。これには次の 3 つの問題があります。

    内部オブジェクトが多数あります。要素が格納されるたびに、平均 4 つのオブジェクト (インデックス + ノード + キー + 値、平均レイヤーの高さは 1) が必要になります。新しく挿入されたオブジェクトは若い領域に、古いオブジェクトは古い領域にあります。 ParNew アルゴリズムの CardTable マークでも、G1 アルゴリズムの RSet マークでも、要素が連続して挿入されると内部の参照関係が頻繁に変化するため、古い領域のスキャンがトリガーされる可能性があります。
  1. ビジネスによって書き込まれた KeyValue 要素が通常の長さではなく、古い領域に昇格されると、大量のメモリの断片が生成される可能性があります。
  2. 問題 1 により、若いエリア GC のオブジェクト スキャン コストが非常に高くなり、若い GC 中により多くのオブジェクトがプロモートされます。問題 2 では、若い GC 中にスキャンする必要がある古い領域が拡大します。問題 3 により、メモリの断片化によって FGC が発生する可能性が高くなります。書き込まれる要素が小さくなると、問題はさらに深刻になります。オンラインの RegionServer プロセスに関する統計を作成したところ、1 億 2,000 万ものアクティブなオブジェクトがあることがわかりました。
  3. 現在の若い GC の最大の敵を分析した結果、書き込みキャッシュの割り当て、アクセス、破壊、リサイクルはすべて私たちが管理するため、JVM が書き込みキャッシュを「認識できない」場合、私たちは大胆なアイデアを思いつきました。ライト キャッシュのライフ サイクルを自分たちで管理すれば、GC 問題は自然に解決されます。

  4. JVM を「不可視」にするというと、オフヒープの解決策を思い浮かべる人が多いかもしれませんが、書き込みキャッシュの場合はそれほど単純ではありません。KeyValue をオフヒープに配置したとしても、問題 1 と 1 は回避できないからです。質問2。そして 1 と 2 は若い GC にとって最大の問題でもあります。

質問は、「JVM オブジェクトを使用せずに同時アクセスをサポートする順序付けされたマップを構築する方法」に変換されました。

もちろん、Map の書き込み速度は HBase の書き込みスループットに密接に関係しているため、パフォーマンスの低下を受け入れることはできません。

オブジェクトを使用せず、パフォーマンスを損なうことなく同時アクセスをサポートする順序付けされたマップを構築する方法という要求が再び強化されています。

この目標を達成するために、次のようなデータ構造を設計しました:

  • 連続メモリ (ヒープ内またはヒープ外) を使用し、JVM のオブジェクト メカニズムに依存するのではなく、コードを通じて内部構造を制御します。

  • 論理的には、これはロックフリーの同時書き込みとクエリをサポートする SkipList でもあり、コントロール ポインターとデータは連続メモリ構造に保存されます。 ライト キャッシュ メモリを大きなメモリ セグメント (チャンク) の形式で申請します。各チャンクには複数のノードが含まれており、各ノードは要素に対応します。新しく挿入された要素は常に、使用されているメモリの最後に配置されます。 Node内部の複雑な構造には、メンテナンス情報やIndex/Next/Key/Valueなどのデータが格納されています。新しく挿入された要素は、ノード構造にコピーする必要があります。 HBase で書き込みキャッシュ ダンプが発生すると、CCSMap 全体のすべてのチャンクがリサイクルされます。要素が削除されると、その要素はリンク リストから論理的に「キック」されるだけで、実際にメモリから要素を回復することはありません (もちろん、実際に回復する方法はありますが、HBase に関する限り、必要ありません)。

  • KeyValue データを挿入するときに余分なコピーが発生しますが、ほとんどの場合、コピーした方が高速です。 CCSMap の構造上、Map 内の要素の制御ノードと KeyValue はメモリ内で隣接しているため、CPU キャッシュを使用する方が効率が良く、シークが高速になります。 SkipList の場合、書き込み速度は実際にはシーク速度によって制限され、実際のコピーによって生じるオーバーヘッドはシーク オーバーヘッドよりもはるかに小さくなります。当社のテストによると、JDK 付属の ConcurrentSkipListMap と比較して、50Byte 長の KV テストで読み取りおよび書き込みのスループットが 20 ~ 30% 向上しました。
  • JVM オブジェクトがないため、各 JVM オブジェクトは少なくとも 16 バイトのスペースを占有し、保存できます (8 バイトはタグ用に予約され、8 バイトは型ポインターです)。 50 バイト長の KeyValue を例にとると、JDK に付属の ConcurrentSkipListMap と比較して、CCSMap のメモリ使用量は 40% 削減されます。

  • CCSMap が運用環境で開始された後、実際の最適化効果: 若い GC が 120 ミリ秒以上から 30 ミリ秒に短縮されました

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。最適化前

最適化後

CCSMap の使用後、元の 1 億 2,000 万個の存続オブジェクトが数十ミリ秒以内に減少しました数百万レベルになると、GC 圧力が大幅に低下します。コンパクトなメモリ配置により、書き込みスループットも 30% 向上しました。

キャッシュ: BucketCache

HBase はディスク上のデータをブロックの形式で編成します。一般的な HBase ブロック サイズは 16K ~ 64K です。 HBase は、ディスク I/O を削減するために BlockCache を内部的に維持します。 BlockCache は、書き込みキャッシュと同様に、GC アルゴリズム理論の世代仮説に準拠しておらず、本質的に GC アルゴリズムにとって好ましくありません。一時的でも永続的でもありません。

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。 ブロック データの一部がディスクから JVM メモリにロードされ、そのライフ サイクルは数分から数か月に及び、ほとんどのブロックは古い領域に入り、メジャー GC 中にのみ JVM によってリサイクルされます。その問題点は主に次の点に反映されます:

HBase ブロックのサイズが固定されておらず、比較的大きく、メモリが断片化されやすい Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

ParNew アルゴリズムでは、プロモーションが面倒です。この問題はコピーのコストには反映されませんが、サイズが大きいことと、HBase ブロックを保存するための適切なスペースを見つけるのにコストがかかることが原因です。

読み取りキャッシュの最適化の考え方は、ブロックがメモリにロードされるときに、BlockCache として返されることのないメモリ部分を JVM に適用することです。 、ブロックをセグメント化された範囲にコピーし、使用済みとしてマークします。このブロックが不要になると、その間隔が使用可能としてマークされ、新しいブロックを再保存できるようになります。これが BucketCache です。 BucketCache でのメモリ空間の割り当てとリサイクルについて (この領域の設計と開発は何年も前に完了しています)

BucketCache

オフヒープ メモリに基づく多くの RPC フレームワークは、オフヒープ メモリ自体の割り当てとリサイクルも管理します一般に、メモリは明示的な解放によって再利用されます。ただし、HBase にはいくつかの困難があります。 Block オブジェクトは、自己管理が必要なメモリ セグメントと考えられます。ブロックは複数のタスクによって参照される可能性があります。ブロックのリサイクルの問題を解決する最も簡単な方法は、ブロックをタスクごとにスタックにコピーし (コピーされたブロックは通常、古い領域にプロモートされません)、それを JVM に転送することです。管理。

実際、私たちは以前にもこの方法を使用していました。実装が簡単で、JVM が承認し、安全で信頼性があります。しかし、これは損失の多いメモリ管理方法であり、GC 問題を解決するために、リクエストごとにコピー コストが導入されます。スタックへのコピーには追加の CPU コピー コストと若い領域のメモリ割り当てコストが必要となるため、CPU とバスがますます貴重になっている現在、この価格は高く感じられます。

そこで、HBase で発生する主な問題点は、参照カウントを使用することにしました:

  1. 同じブロックを参照する複数のタスクが HBase 内に存在する可能性があります

  2. 同じタスク変数内に複数のタスクが存在する可能性があります。同じブロックを参照します。参照は、スタック上の一時変数またはヒープ上のオブジェクト フィールドである可能性があります。

  3. Block の処理ロジックは、パラメーター、戻り値、フィールド割り当ての形式で複数の関数とオブジェクト間で受け渡されます。

  4. ブロックは当社によって管理される場合もあれば、管理されない場合もあります(手動で解放する必要があるブロックもあれば、そうでないブロックもあります)。

  5. ブロックはブロックのサブタイプに変換される場合があります。

これらの点を総合すると、正しいコードを書くのは困難です。しかし、C++ では、スマート ポインタを使用してオブジェクトのライフサイクルを管理するのが自然です。なぜ Java ではそれが難しいのでしょうか。

Java の変数代入は、ユーザー コード レベルでは参照代入動作のみを生成しますが、C++ の変数代入ではオブジェクトのコンストラクターとデストラクターを使用して多くのことを行うことができ、スマート ポインターはこれに基づいて実装されます (もちろん、 C++ のコンストラクターとデストラクターを不適切に使用すると、多くの問題が発生しますが、それぞれに独自の長所と短所がありますが、ここでは説明しません)

そこで、C++ のスマート ポインターを参照し、ブロック参照管理およびリサイクル フレームワーク ShrableHolder を設計しました。それ以外の場合はコーディングが困難です。これには次のパラダイムがあります:

  1. ShrableHolder は、参照カウントされるオブジェクトと参照カウントされないオブジェクトを管理できます。

  2. ShrableHolder は、再割り当てされるときに前のオブジェクトを解放します。管理対象オブジェクトの場合、参照カウントは 1 減算されますが、管理対象オブジェクトでない場合は、変更はありません。

  3. ShrableHolderは、タスク終了時またはコードセグメント終了時にresetを呼び出す必要があります

  4. ShrableHolderを直接割り当てることはできません。コンテンツを転送するには、ShrableHolder によって提供されるメソッドを呼び出す必要があります

  5. ShrableHolder を直接割り当てることができないため、ライフサイクル セマンティクスを含むブロックを関数に渡す必要がある場合、ShrableHolder を関数のパラメーターとして使用することはできません。

このパラダイムに従って書かれたコードには、元のコードに対する論理的な変更はほとんどなく、if else は導入されていません。まだ多少の複雑さはあるようですが、幸いなことに、これによる影響を受ける範囲は依然として非常にローカルな下位層に限定されており、HBase ではまだ許容可能です。安全を期してメモリ リークを回避するために、長期間非アクティブな参照を検出する検出メカニズムをこのフレームワークに追加しました。検出されると強制的に削除対象としてマークされます。

BucketCache を適用すると、BlockCache のプロモーション オーバーヘッドが減少し、若い GC 時間が短縮されます:

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

(CCSMap+BucketCache 最適化の効果)

ZenGC についての簡単な説明

上記の後で最適化後、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 ミリ秒に最適化しました。

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

(ZenGC 最適化効果、ユニットの問題、ここにあります)

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

Java のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。

クラウドでの使用

Ali-HBase は現在、Alibaba Cloud で商用サービスを提供しており、ニーズがある人は誰でもすべてのユーザーが利用できますAlibaba Cloud 上で大幅に改善されたワンストップの HBase サービスを使用します。クラウド HBase バージョンは、自社構築の HBase と比較して、運用と保守、信頼性、パフォーマンス、安定性、セキュリティ、コストの点で多くの点で改善されています。

関連記事:

Java ガベージ コレクションのオーバーヘッドを削減するための 5 つの提案

Java ガベージ コレクション

関連ビデオ:

ガベージ コレクションのメカニズム - Han Shuping 2016 最新の PHP オブジェクト指向プログラミング ビデオ チュートリアル

以上がJava のガベージ コレクション時間も簡単に短縮できます。Ali-HBase の GC を例に説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。