I.はじめに
Javar にとってガベージ コレクションは避けては通れないトピックであり、作業に伴うチューニング作業はガベージ コレクターを中心に展開することがよくあります。さまざまなビジネス シナリオに直面すると、GC パフォーマンスを保証できる統合ガベージ コレクターは存在しません。したがって、プログラマはビジネス コードを作成できるだけでなく、JVM の基礎となる原理とチューニングに関する知識も持っている必要があります。この状況は ZGC の登場により変わる可能性があり、新世代コレクタ ZGC ではチューニングがほとんど不要で、GC の一時停止時間を 1 秒以下のレベルに短縮できます。
Oracle は JDK11 から ZGC を正式に導入しました。ZGC には 3 つの大きな目標があります。
- TBレベルのメモリ(8M~4TB)をサポートします。
- 一時停止時間は 10 ミリ秒以内に制御され (本番環境での実際の観測値はマイクロ秒レベルです)、ヒープのサイズやアクティブなオブジェクトのサイズによって一時停止が増加することはありません。
- プログラムのスループットへの影響は 15% 未満です。
ZGC はこの目標を達成するためにどのように設計されていますか? この記事では、ZGC アルゴリズムの主要な特性から始め、ZGC サイクル処理プロセスを分析することでこれらの特性を理解し、ZGC 設計のアイデアを検討します。
2. ZGC の用語
非世代: メモリは新しい世代と古い世代に分割され (G1 は論理的に世代です)、ZGC は世代設計をキャンセルし、各 GC サイクルはヒープ全体内のすべてのアクティブ オブジェクトをマークします。
ページ: ZGC はヒープ スペースをページと呼ばれる領域に分割し、ページを使用してメモリを再利用します。
同時実行性: GC、スレッド、ビジネス スレッドは同時に実行されます。ZGC の高度な同時実行設計により、ほとんどすべての GC 作業、マーキング、およびヒープのデフラグが、短い STW 同期一時停止を含めて、ビジネス スレッド (ミューテーター) と同時に実行されます。
並列:複数のスレッドが GC スレッドで同時に動作し、リサイクルを高速化します。
マークコピーアルゴリズム:マークコピーアルゴリズムは主に以下の 3 つの処理から構成されます。
- マーキング フェーズは GC ルート コレクションから開始され、オブジェクトの到達可能性を分析し、アクティブなオブジェクトをマークします。
图1:可达性分析后对象的引用状态
- オブジェクト転送フェーズでは、アクティブなオブジェクトを新しいメモリ アドレスにコピーします。
- 再配置フェーズでは、転送によりオブジェクトのアドレスが変更されるため、オブジェクトの古いアドレスを指すすべてのポインタをオブジェクトの新しいアドレスに調整する必要があります。
マーク コピー アルゴリズムの最大の利点は、ヒープ メモリの断片化の発生を防ぎ、コピー プロセス中にヒープ メモリを整理できることです。ZGC、CMS、および G1 はすべてマークコピー アルゴリズムを使用しますが、実装が異なるとパフォーマンスに大きな違いが生じます。
3. ZGCの性能データ
ZGC 設計は、スループットが影響を受けないようにしながら、最大停止時間を数ミリ秒に抑えるよう努めています。以下は、OpenJDK のさまざまなコレクターに対して SPECjbb2015 によって実行されたパフォーマンス テスト データです。128G ヒープ メモリでは、ZGC のパフォーマンスは、レイテンシーとスループットの点で他のコレクターよりも優れています。
图2:SPECjbb2015GC性能评分
图3: SPECjbb2015GC延迟比较
4. ZGCの主な特徴
ZGC のサイクルは同時実行性が高く、同時実行性が高いほど、GC 動作時のビジネス スレッドへの影響が小さくなります。SPECjbb2015 のパフォーマンス レポートでは、ZGC のレイテンシーが G1 よりも 10 倍以上低いことがわかります。ZGC は、作業サイクルは 3 つだけで、ステージは STW であり、他のステージは完全に同時実行されます。これは、ZGC によるヒープ ビューの同時実行性と一貫性設計の改善によるものです。同時シナリオでは、共有リソースの一貫性を達成するためにさまざまなスレッドを調整する必要があることは誰もが知っています。一般的な方法は、リソースをロックすることです。ガベージ コレクターの考え方も同様です。GC スレッドが動作している場合、GC スレッドは、オブジェクト リソースを処理するには、すべてのビジネス スレッドを一時停止する必要があり、その結果 STW (Stop The Word) が発生します。以前は、ガベージ コレクターによって GC スレッドとビジネス スレッドがヒープ内のオブジェクトのアドレスを一致させていましたが、オブジェクトが転送されると、ビジネス スレッドは、オブジェクトのアドレスが変更されたにもかかわらず、そのオブジェクトにアクセスできなくなりました。 G1 オブジェクトまたは CMS オブジェクトがコピーされるかどうか。STW は常に必要です。ZGC が使用する Colored Pointer および Load Barrier テクノロジにより、すべてのスレッドがオブジェクト アドレスではなく、同時条件下でポインタの色 (状態) に同意することができます。したがって、ZGC はオブジェクトを同時にコピーできるため、GC の一時停止時間が大幅に短縮されます。まず、カラー ポインターと読み取りバリアについて予備的に理解し、次に、ZGC リサイクル サイクルを通じてこれら 2 つのテクノロジーの具体的なアプリケーションを見ていきます。
カラーポインター
ポインターにメタデータを埋め込み (アドレスの上位ビットを使用して実装)、ポインターにメタデータを格納するこのテクノロジは、カラー ポインターと呼ばれます。ZGC のポインターは常に 64 ビット構造で、メタ ビット (ポインターの色) とアドレス ビットで構成されます。アドレス ビットの数により、理論的にサポートされる最大ヒープ サイズが決まります。ZGC は 42 ビットのストレージ アドレスを使用します。つまり、ZGC は最大 4TB のヒープ メモリをサポートします。図に示すように、下位 42 ビットはアドレス ビット、中位 4 ビットはメタ ビット、上位 18 ビットは未使用です。4 つのビットは、Finalized (F)、Remapped (R)、Marked1 (M1)、Marked0 (M0) です。
图4: 64位地址使用示意图
ZGC では、指定されたマークは色で表され、「良好」 (アドレスが有効である) または「不良」 (アドレスが無効である可能性がある) になります。ポインタの色は、そのビットの状態 (F、R、M1、および M0) によって決まります。「良好」とは、R、M1、M0 ビットの 1 つがセットされ、他の 3 つがセットされていないことを意味します。たとえば、0100、0010、および 0001 は「良好」色に属します。オブジェクトの状態は、追加のメモリ アクセスを行わずにポインタの色で区別できるため、マーキングおよび転送フェーズで ZGC が高速になります。
アドレス ビットの状態を設定することにより、異なるアドレス ビューを形成できます。ZGC の同じ物理ヒープ メモリが仮想アドレス空間に 3 回マッピングされ、同じ物理メモリの 3 つの「ビュー」が生成されます。 GC アクティビティのさまざまな期間でアクティブなビューを表示します。ZGC は、ガベージ コレクション サイクルに従って異なるビュー マークを切り替えることによってオブジェクトの色をマークします。
次の図は、仮想アドレスの空間分割を示しています。
图5:虚拟地址空间划分和多视图映射
[0~4TB)はJavaヒープに相当します。
[4TB ~ 8TB) は M0 アドレス空間と呼ばれます。
[8TB ~ 12TB) は M1 アドレス空間と呼ばれます。
[12TB ~ 16TB) 予約済みおよび未使用。
[16TB ~ 20TB) はリマップされた領域と呼ばれます。
ZGC は世代別ではないため、ガベージ コレクションはヒープ領域全体をスキャンする必要があり、アドレス ビューは Java ヒープ全体を複数の部分に分割し、各部分に仮想メモリ セグメントを割り当てます。ガベージ コレクション中に、ZGC は仮想メモリ セグメントの 1 つをスキャンし、それを現在のビューとして実際のメモリの場所にマップするだけで済みます。同時に、ZGC は他の仮想メモリ セグメントを仮想アドレスにマップし、これらのメモリ セグメントはコレクタによってスキャンされません。
ロードバリア
ZGC は、書き込みバリアの代わりに読み取りバリアを利用する点で、HotSpot JVM の以前の GC (CMS、G1 など) アルゴリズムとは大きく異なります。読み取りバリアは、同時転送中のオブジェクト ポインタ更新の問題を解決します。転送中に、参照先オブジェクトの受信ポインタを更新せずにオブジェクトが移動された場合 (移動されたオブジェクトはヒープ内の他のオブジェクトによって参照される可能性があります)、ダングリング ポインタが発生します。 (解放されたメモリ空間または無効なメモリ アドレスは、ダングリング ポインタにアクセスするときに問題を引き起こします)。リード バリア テクノロジは、そのようなダングリング ポインタ オブジェクトをキャプチャし、コードをトリガーしてオブジェクトの新しい位置を更新することで、ダングリング ポインタを「修復」できます。ロード時にダングリング ポインタが修正されるようにオブジェクトがどのように移動するかを追跡するために、ZGC では転送テーブルを使用して、再配置前 (古い) アドレスを再配置後 (新しい) アドレスにマッピングします。ビジネス スレッドがコンシューマとしてオブジェクトにアクセスする場合でも、GC スレッドが (マーキング中に) ヒープ内のすべてのアクティブなオブジェクトを走査する場合でも、読み取りバリアがトリガーされる可能性があります。
ZGC 読み取りバリアを実装するにはどうすればよいですか? たとえば、コード var x = obj.field です。x はスタック上にあるローカル変数、field はヒープ上にあるポインターです。ビジネス スレッドは、ヒープ オブジェクトを操作するときに読み取りバリアをトリガーします。リード バリアの実行パスには、高速パスと低速パスの 2 つがあり、ロードされるポインタが有効な状態 (良好な色) にある場合は高速パスが使用され、それ以外の場合は低速パスが使用されます。高速パスは事実上空ですが、低速パスには有効な状態ポインタを計算するロジックが含まれています。つまり、オブジェクトが再配置された (または再配置される) かどうかを確認し、再配置された場合は、新しいアドレスを検索または生成します。読み取りバリアをトリガーするスレッドが最新のアドレスを読み取ることができることに加えて、読み取りバリアには自己修復ポインターの機能もあります。つまり、読み取りバリアはポインターの状態を変更して、他のスレッドによる後続のアクセスが可能になります。パスをすばやく実行できます。どちらのパスを選択しても、正しい状態のアドレスが返されます。次の疑似コードは、読み取りバリアを実行するときの ZGC の一般的なロジックを表すために使用されます。
/**
slot 是值线程栈中的局部变量,也就是屏障要操作的目标对象
*/
unintptr_t barrier(unintptr_t *slot,unintptr_t addr){
//快速路径,fast path
if(is_good_or_null(addr))return addr;
//慢速路径,slow path
good_addr = process(addr);
//自我修复
self_heal(slot,addr,good_addr);
return good_addr;
}
/*
自我修复,将指针恢复到正常状态
*/
void self_heal(unintptr_t *slot,unintptr_t old_addr,unintptr_t new_addr){
if(new_addr == 0)return;
while(true){
if(CAS(slot,&old_addr,new_addr)
return;
if(is_good_or_null(old_addr))
return;
}
}
ZGC の読み取りバリアは、GC スレッドおよびビジネス スレッドによってトリガーされる可能性があり、ヒープ内のオブジェクトにアクセスする場合にのみトリガーされます。アクセスされたオブジェクトが GC ルートにある場合にはトリガーされません。これが、GC ルートをスキャンするときに STW が必要な理由です。 。
以下は、読み取りバリアがいつトリガーされるかを示す簡略化されたコード例です。
Object o = obj.FieldA // 从堆中读取引用,需要加入屏障
<Load barrier>
Object p = o // 无需加入屏障,因为不是从堆中读取引用
o.dosomething() // 无需加入屏障,因为不是从堆中读取引用
int i = obj.FieldB //无需加入屏障,因为不是对象引用
5. ZGC実行サイクル
以下の図に示すように、ZGC サイクルは 3 つの STW 一時停止と、マーク/リマップ (M/R)、同時参照処理 (RP)、同時転送準備 (EC)、および同時転送 (RE) の 4 つの同時実行フェーズで構成されます。読者がすぐに理解できるように、ZGC の実行プロセスは以下で大幅に簡略化されています。
图6:ZGC周期表示
イニシャルマーク(STW1)
ZGC 初期マークの実行は 3 つの主要なタスクで構成されます。
- アドレスビューは M0 (または M1) に設定され、前のサイクルに従って M0 または M1 が交互に設定されます。
- 新しいページをビジネス スレッドに再割り当てしてオブジェクトを作成すると、ZGC は現在のサイクルより前に割り当てられたページのみを処理します。
- 初期マーキング: 生き残ったルート オブジェクトのみが M0 (M1) としてマークされ、同時マーキングのためにマーキング スタックに追加されます。
GC サイクル中のアドレス表示ウィンドウ
图7:ZGC周期中状态窗口划分
同時実行マーキング (M/R)
同時にマークされたタスクが 2 つあります。
まず、同時マーキング スレッドは、マークされるオブジェクト リストから開始し、オブジェクト参照関係グラフに従ってオブジェクトのメンバー変数をたどって、再帰的にマークします。
次に、関連するページのアクティビティ情報を計算して更新します。アクティビティ情報はページ上のアクティブなバイト数であり、再利用されるページを選択するために使用され、それらのオブジェクトはヒープの最適化の一環として再配置されます。
次の疑似コードは、同時マーキングの主なプロセスです。
while(obj in mark_stack){
//标记存活对象,当且仅当该对象未被标记并且当前线程成功标记该对象时才返回true
success = mark_obj(obj);
if(success){
for(e in obj->ref_fields()){
MarkBarrier(slot_of_e,e);
}
}
}
//GC线程调用
//EC是待回收页面的集合
void MarkBarrier(uintptr_t *slot,unintptr_t addr){
if(is_null(addr))return;
//判断是否在待回收集合内
if(is_pointing_into(addr,EC)){
//地址重映射到当前GC视图
good_addr = remap(addr);
} else {
good_addr = good_color(addr);
}
//访问的对象添加到标记栈
mark_stack->add(good_addr);
self_heal(slot,addr,good_addr);
}
//读屏障前面有介绍过,由业务线程调用
void LoadBarrier(uintptr_t *slot,unintptr_t addr){
if(is_null(addr))return;
if(is_pointing_into(addr,EC)){
good_addr = remap(addr);
} else {
good_addr = good_color(addr);
}
mark_stack->add(good_addr);
self_heal(slot,addr,good_addr);
return good_addr;
}
コード スニペットは、同時マーキング フェーズ中の GC スレッドのメイン ループを示しています。mark_obj() は、オブジェクトがマークされておらず、現在のスレッドがオブジェクトを正常にマークした場合にのみ true を返します。内部的にアトミック操作 (比較とスワップ、CAS) を使用してビットマップ内のビットを設定するため、スレッドセーフです。MarkBarrier() は、オブジェクトのメンバー プロパティを走査し、オブジェクト参照のマーキングを完了します。ビジネス スレッドは同時マーキング中にも実行されます。以前は、ビジネス スレッドがオブジェクトにアクセスすると、LoadBarrier() が実行されて、GC スレッドによるオブジェクトのマーキングの完了を支援していました。
リマーキングフェーズ(STW2)
再ラベル付けフェーズには 3 つの主なタスクがあります。
- 修復タスクの実行は、スレッドが C2 によってコンパイルされたコードを実行することを意味し、再マーキング段階に入るときに欠落マークが発生する可能性があります。
- マーキングの終了。同時マーキングの後、ビジネス スレッドのローカル マーキング スタックにマークされるオブジェクトが存在する場合があります。このステップを実行する目的は、これらのマークされるオブジェクトをマークすることです。
- 部分的な非強力なルートの平行マーキングを実行します。
同時転送準備(EC)
同時転送準備タスク:
- リサイクル可能なすべてのページをフィルタリングする
- ゴミの多いページをページ転送セットとして選択する
初期転送(STW3)
初期転送には主に次のプロセスが含まれます。
- アドレス ビューの調整: アドレス ビューを M0 または M1 から再マップに調整します。これは、実際の転送を開始することを意味します。その後、割り当てられたすべてのオブジェクト ビューが再マップされます。
- TLAB の再配置: アドレス ビューが調整されるため、TLAB 内のアドレスのビューを調整する必要があります。
- 転送の開始: ルート コレクションから開始して、ルート オブジェクトによって直接参照されているオブジェクトを走査し、これらのオブジェクトを転送します。
初期転送は STW であり、その処理時間は GC ルートの数に比例し、通常はほとんど時間がかかりません。
同時転送(RE)
初期転送により GC ルート オブジェクトの再配置が完了します。同時転送フェーズでは、前の手順で決定された転送セット (EC) が転送され、転送セットの各ページで転送が実行されます。
同時転送のプロセスは、次の疑似コード プロセスに抽象化できます。
//GC线程主循环遍历EC的页面,将个将EC集页面中对象进行转移
for (page in EC){
for(obj in page){
relocate(obj);
}
}
//该方法GC和业务线程都有可能执行,如果是业务线程访问对象会先进行转移在进行操作
unintptr_t relocate(unintptr_t obj) {
//获取对象的地址转发表
ft = forwarding_tables_get(obj);
if (ft->exist(obj)){
return ft->get(obj);
}
new_obj = copy(obj);
//CAS写对象转发表数据
if(ft->insert(obj,new_obj)){
return new_obj;
}
//CAS发生竞争,写转发表失败,释放分配的内存
dealloc(new_obj)
return ft->get(obj);
}
フォワーディングテーブルの機能は、転送後の旧アドレスから新アドレスへのマッピングを保存することであり、フォワーディングテーブルのデータはページに格納され、転送完了後のページは再利用することができます。
同時転送が完了すると、ZGC サイクル全体が完了します。
6. ZGCアルゴリズムのデモンストレーション
ZGC アルゴリズムを説明するために、以下の図は例のすべてのステージを示しています。
图8:ZGC算法演示
図 8(1) はヒープの初期状態を示しており、アプリケーション起動後、ZGC は初期化を完了しています。
図 8(2) では、グローバル マークとして M0 が選択されており、すべてのルート ポインタが M0 としてマークされています。その後、すべてのルートがマーク スタックにプッシュされ、マーク スタックは同時マーク (M/R) 中に GC スレッドによって消費されます。
図 8(3) に示すように、ポインタに状態がある場合でも、オブジェクト自体は適切な色で描画され、マークされていることを示します。
図 8(4) では、生き残ったオブジェクトが最も少ないページ (中央のページ) が転送候補セット (EC) として選択されます。
続いて、図 8(5) では、グローバル フラグが Remmaped に設定され、すべてのルート ポインタが Remmaped に更新されています。ルートが EC を指している場合、対応するオブジェクトが再配置され、ルート ポインタが新しいアドレスに更新されます。
図 8(6) では、EC 内のオブジェクトが転送され、新旧アドレス変換用ページの転送テーブルからアドレス レコードが削除されます。同時転送フェーズが終了すると、現在の GC サイクルも終了します。EC 全体は現在のサイクル中にリサイクルされます。ここで質問があるかもしれません。オブジェクトの古いアドレスは更新されていません。ページがリサイクルされた場合でも、どのようにしてオブジェクトに引き続きアクセスできますか? その理由は、ページ内のオブジェクト ストレージ領域はリサイクルされ、転送テーブルはリサイクルされないためです。この時点でビジネス スレッドがこれらのオブジェクトにアクセスすると、読み取りバリアのスロー パス ビットがトリガーされ、無効なポインタがトリガーされます。修理されます。アクセスされなかった無効なポインタは、次の GC 同時マーク (M/R) フェーズまで修復されません。
図 8(7) では、次の GC サイクルが開始され、M1 がグローバル状態として選択されます (M0 と M1 が交互に切り替わります)。
図 8(8) では、無効なインジケータについて転送テーブルをクエリすることによって、同時マーキング フェーズ (M/R) が新しい場所にマッピングされます。
最後に、図 8(9) では、前のサイクルの EC ページの転送テーブルが再利用されて、次の同時転送 (RE) フェーズに備えます。
7. まとめ
ZGC は非常に複雑な JVM サブシステムであり、1 つの記事ですべての詳細を説明することは不可能です。この記事では、ZGC のカラー ポインタとリード バリアの主要なテクノロジについて詳しく説明します。これらは ZGC の革新的な点でもあります。最後に、ZGC アルゴリズム プロセスの簡略化されたバージョンを例を通して示します。また、ZGC のような複雑なシステムを学ぶことで、複雑なシステムを分析する際には、最初から実装の詳細をあまり気にする必要はなく、重要なプロセスから始めて、その後深く掘り下げていくことができることに気づきました。
ZGC の高同時実行設計は、カラー ポインターとリード バリアの使用に起因する高いパフォーマンスに貢献しています。もちろん、これら 2 つの項目に加えて、メモリ モデル、同時実行モデル、予測などの優れた設計もあります。アルゴリズムなどについてはここでは説明しません。読者の方は他の記事を参照してください。ZGC の基本原理を理解すると、アプリケーションのパフォーマンスを最適化し、アプリケーションをチューニングするための知識を構築するのに役立ちます。最後に、ZGC はパフォーマンスと安定性に優れているため、GC を選択する際には ZGC を優先して使用することができます。
参考内容:
[1] Peng Chenghan: 「新世代のガベージ コレクター ZGC の設計と実装」機械産業プレス、2019 年。
[2] https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html
[3] https://www.baeldung.com/jvm-zgc-garbage-collector
[4] https://openjdk.org/projects/zgc/
[5] https://www.jfokus.se/jfokus18/preso/ZGC--Low-Latency-GC-for-OpenJDK.pdf
*文/byteyangyang
この記事は Dewu Technology のオリジナルです。さらに興味深い記事については、Dewu Technology 公式 Web サイトを参照してください。
Dewu Technology の許可なく転載することは固く禁じられています。さもなければ、法律に従って法的責任が追及されます。
雷軍氏: Xiaomi の新オペレーティング システム ThePaper OS の正式版がパッケージ化されました Gome App の宝くじページのポップアップ ウィンドウが創設者を侮辱 米 政府が NVIDIA H800 GPU の中国への輸出を制限 Xiaomi ThePaper OS インターフェース マスターが Scratch を使用して RISC-V シミュレータを操作し、正常に実行されました Linux カーネル RustDesk リモート デスクトップ 1.2.3 がリリースされ、Wayland サポートが強化されました Logitech USB レシーバーを取り外した後、Linux カーネルがクラッシュしました DHH の「パッケージング ツール」のシャープ レビュー": フロントエンドはまったくビルドする必要がありません (No Build) JetBrains が技術文書を作成するために Writerside を起動 Node.js 21 用ツールが正式リリース