ZGC | What’s new in JDK 15

原文はこちら。
The original article was written by Per Lidén (software engineer, Oracle).
https://malloc.se/blog/zgc-jdk15

JDK 15が9月15日にリリースされました。リリースを祝して、Oracle Developer Live – Javaというバーチャルコンファレンスが開催され、その中で「ZGC – The Next Generation Low-Latency Garbage Collector」というタイトルでセッションを担当しました。全てのセッションが録画されていますので、見逃した方もオンデマンドでご覧いただけます。

JDK 15
https://openjdk.java.net/projects/jdk/15/
Oracle Developer Live—Java
https://developer.oracle.com/developer-live/java/

このエントリでは、JDK 15でZGCに入った興味深い変更点をご紹介します。いつも通り、ZGCについて詳細情報をお求めであれば、OpenJDK WikiやInside JavaのGCの章、もしくはこのブログをご覧ください。

ZGC OpenJDK Wiki
https://wiki.openjdk.java.net/display/zgc
Inside Java – Garbage Collectors
https://inside.java/tag/gc
perliden’s blog
https://malloc.se/

Production ready!

JDK 15で、ZGCはproduction readyになりました。言い換えると、実験的ではない、本番運用可能なJDKの機能になった、ということです。この変更はJEP 377で行われ、多くの人々の数年にわたるハードワークの集大成となりました。

JEP 377: ZGC: A Scalable Low-Latency Garbage Collector (Production)
http://openjdk.java.net/jeps/377

もちろん、これはZGCプロジェクトにとって大きな節目であり、みんなが切望していました。しかし、軽い気持ちで実験的な状態からProduction Readyにしたわけではありませんのでご安心ください。だれもヒープを破損させたり、JVM をクラッシュさせたりする GC を信頼しませんよね。今日のニーズに関連した機能を提供するために、本番環境に対応したGCに期待されているかと思います。

JDK 11での登場以来、これまでのリリースで多くの新機能、着実なパフォーマンスと安定性の向上、一般的に使用されているすべてのプラットフォームでのサポートがZGCに加わっていきましたし、数多くのテストも実施しました。

まとめると、ZGC は安定した高性能、低レイテンシのGCであり、本番のワークロードにも対応できるようになっています。

New features and enhancements

Improved allocation concurrency

通常、Java オブジェクトの割り当ては非常に高速です。新しいオブジェクトの割り当て方法はまさに使用しているGCに依存します。ZGCでは、割り当てパスはいくつかの層を通過します。割り当ての大部分は、非常に高速な第 1 層が賄います。ごく一部の割り当てでは、非常に低速な最後の層まで行く必要があります。

それまでの階層のすべてで割り当てができなかった場合にのみ、最後の階層での割り当てを実施します。これは最後の手段であり、ZGC はヒープを拡張するために、より多くのメモリをコミットするようにオペレーティングシステムに要求します。これも失敗した場合や、ヒープの最大サイズ (-Xmx) に達した場合は、OutOfMemoryErrorがスローされます。

JDK 15までは、ZGCはメモリのコミット(およびアンコミット)中にグローバルロックを保持していました。これはもちろん、1 つのスレッドだけが、いつでもヒープを拡張(または縮小)できることを意味していました。メモリのコミットとアンコミットは、完了するまでに時間がかかる比較的高コストな操作であり、その結果、このグローバル・ロックは時として論争の的になることがありました。

JDK 15では、アロケーション パスのこの部分を再構築し、メモリのコミットおよびアンコミット中にこのロックが保持されなくなっています。その結果、最後の層での割り当て実施時の平均コストが減り、この層での同時割り当て処理能力が大幅に向上しました。

Incremental uncommit

ZGC のアンコミット機能は、JDK 13で最初に導入されました。このメカニズムにより、ZGCは未使用のメモリをアンコミットしてヒープを縮小し、その未使用メモリを他のプロセスが使用できるようにオペレーティングシステムに返却できます。メモリがアンコミットの対象となるためには、ある程度の時間(デフォルトは300秒、-XX:ZUncommitDelay=<seconds>で制御)の間、メモリが未使用である必要があります。ある時点より後により多くのメモリが必要になった場合、ZGC は新しいメモリをコミットしてヒープを再び大きくします。

メモリのアンコミットは比較的高コストな操作であり、この操作が完了するまでにかかる時間は、操作しているメモリのサイズに応じて大きくなる傾向があります。JDK 15より前のバージョンでは、ZGCが2MBまたは2TB のメモリがアンコミットの対象だとしても、OSに対し1回のアンコミット操作を発行するだけでした。これは、数百ギガバイトやテラバイトの大量のメモリのアンコミットにかなりの時間がかかるため、潜在的に問題があることが判明しました。この間、メモリの需要は劇的に変化する可能性がありますが、アンコミット実行中にその操作を中止したり修正したりする方法がZGCにはありませんでした。メモリの需要が増大した場合、まずZGCは実行中のアンコミット操作が完了するのを待たなければならず、その後すぐにそのメモリの一部を再びコミットしなければなりませんでした。

JDK 15では、アンコミットのメカニズムが再構築され、メモリをインクリメンタルにアンコミットするようになりました。1回のアンコミット操作ではなく、ZGCは小さなアンコミット操作を多数回OSに対して発行します。これにより、メモリ需要の変化を速やかに検知し、実行中のアンコミットプロセスを中断したり、修正したりできるようになりました。

Improved NUMA awareness

ZGCは、Java スレッドがオブジェクトを割り当てると、そのオブジェクトは Java スレッドが動作しているCPUのローカルなメモリに行き着くという意味で、Linuxでは常にNUMA (Non-uniform memory access) を認識しています。NUMAマシンでは、CPUローカルなメモリにアクセスすると、メモリレイテンシが低下し、全体的なパフォーマンスが向上します。しかしこれまでは、ZGCがNUMAを認識できていたのは、ラージページを使用している場合(-XX:+UseLargePages)のみでした。これは JDK 15で対処され、現在では、ラージページの使用有無に関わらず、ZGCはNUMAを認識し、効果を発揮するようになっています。

JFR events

以下のJFRイベントが追加され、実験的なものではなくなりました。

ZAllocationStallJava スレッドがアロケーションストールの対象となった場合に生成
ZPageAllocation新たなZPage(ヒープリージョン)の割り当ての都度生成
ZRelocationSet
ZRelocationSetGroup
GCサイクルの都度生成
ヒープのどの部分が圧縮/再生されたかを説明
ZUncommitZGCがJavaヒープの不使用箇所をアンコミット(不使用メモリをOSに返却)する都度生成
ZUnmapZGCがメモリをアンマップする都度生成。
断片化した一連のページをより大きな連続したページとして再度マップする必要がある場合に、ZGCは非同期でメモリをアンマップする。

Java heap on NVRAM

ここ数年のNVRAM(Non-Volatile RAM、不揮発性メモリ)分野の進歩により、このようなメモリは大幅に高速化され、大幅に安価になりました。環境やアプリケーションの種類によっては、Java ヒープ全体を(RAMではなく)NVRAMに配置することは、安価なメモリと引き換えに性能を得るという点で、魅力的な選択肢になる可能性があります。実は、HotSpotの(ZGCを除く)全てのGCはJDK 10以後、-XX:AllocateHeapAtを付けることでNVRAMをサポートしていました。しかしJDK15で、これをZGCでもサポートするようになりました。

JEP 316: Heap Allocation on Alternative Memory Devices
http://openjdk.java.net/jeps/316

Compressed class pointers

HotSpotでは、すべてのJavaオブジェクトは、mark wordとclass pointerの2つのフィールドで構成されるヘッダを持ちます。64ビットCPUでは、これらのフィールドはどちらも通常64ビット幅で、クラスポインタはオブジェクトのクラス(型情報、vtableなど)を記述するメモリへのプレーンなポインタです。Compressed Class Pointer機能(圧縮クラスポインタ、 -XX:+UseCompressedClassPointers)は、すべてのオブジェクトヘッダのサイズを小さくすることで、全体的なヒープ使用量を減らすのに役立ちます。これは、クラスポインタフィールドを(64ビットではなく)32ビットに圧縮することによって実現します。Compressed Class Pointerは、プレーンなポインタと異なり、既知のベースアドレスを持つCompressed Class Space(圧縮クラス空間)へのオフセットです。実際のクラスポインタを取得するには、JVMはCompressed Class Spaceのベースアドレスに(ビットシフトされている可能性がある)Compressed Class Pointerを追加するだけです。

Compressed Class Pointer機能の実装は、歴史的にCompressed Oops機能と結びついていましたが、これはCompressed Oopsも有効にしないとCompressed Class Pointerを有効にできないことを意味していました。一方を有効にして他方を有効にできない技術的な理由はないため、これは人為的な依存関係にすぎません。現在、ZGCはCompressed Oopsをサポートしていないので、これといった理由もなく、ZGCでもCompressed Class Pointerの使用ができませんでした。JDK 15では、Compressed Class PointerとCompressed Oopsの人為的な依存関係をなくし、結果としてZGCはCompressed Class Pointerを使えるようになりました。

Class data sharing

HotSpotのクラスデータ共有(Class Data Sharing、CDS)機能は、起動時間短縮と複数のJVMインスタンス間でのメモリフットプリントの削減に寄与します。この機能はCompressed Oops機能が有効化されている場合(-XX:+UseCompressedOops)にのみ動作していましたが、JDK 15ではCDSを機能強化し、Compressed Oops機能が無効であっても動作するようになりました。結果として、CDSは(Compressed Oops機能が無効の)ZGCでも動作するようになっています。

Class Data Sharing
https://docs.oracle.com/en/java/javase/15/vm/class-data-sharing.html

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中