JDK 15 G1/Parallel GC changes

原文はこちら。
The original article was written by Thomas Schatzl (Oracle).
https://tschatzl.github.io/2020/09/01/jdk15-g1-parallel-gc-changes.html

JDK 15 がrelease candidateフェーズに移行し、OpenJDKの stop-the-world ガベージコレクタであるG1とParallel GCの重要な変更についてまとめるのによい時期なので、今回はブログの形でまとめました。

GC領域におけるJDK 15での大きな変更点は、ZGCがproduction readyの機能になった点がありますが、G1とParallel GCに関して言うと、JDK 15ではJEP リストにあるようにこれらのGCに関する大きな機能リリースというよりは、メンテナンスに重きをおいています。一時停止時間の改善の数パーセントを占める通常の微細な最適化に続いて、特に強調したい興味深い変更があります。

JEP 377: ZGC: A Scalable Low-Latency Garbage Collector (Production)
https://openjdk.java.net/jeps/377
JDK 15
https://openjdk.java.net/projects/jdk/15/

Parallel GC

はい、Parallel GCはまだ死んでいませんし、まだまだ改善しています。

JDK-8246718 でGC一時停止時間が改善しました。

[JDK-8246718] ParallelGC should not check for forward objects for copy task queue
https://bugs.openjdk.java.net/browse/JDK-8246718

生存オブジェクトをコピーする内部ループを比較している間に、G1とParallel GCでは大きな違いがありました。最近コピーされたオブジェクトからの新しい参照に遭遇した場合、G1はその参照をプリフェッチしてすぐにタスクキューにプッシュして処理を進めますが、対してParallel GCの場合は、オブジェクトが既にコピーされているかどうかを最初に調べようとしていました。これにより、オブジェクトへのアクセスにおけるキャッシュミスが多発し、パフォーマンスが低下していました。Specjbb2015では、内部ベンチマークで最大 5%のthroughput-under-latency (critical-jOPS) のスコアが改善しました。これはおおよそ一時停止時間に相当します。参照されているCRでさらにいくつかの数値が提供されています。

Standard Performance Evaluation Corporation
https://www.spec.org/jbb2015/

JDK 14におけるJDK-8204951で同じparallel workerスレッド管理機構を使うようにParallel GCを変更した後、コミュニティがこの機構を使ってそれまで直列だったフェーズを並列化し始めました。例えばJDK-8240440などがそれにあたります。

[JDK-8204951] Investigate to use WorkGang for Parallel GC
https://bugs.openjdk.java.net/browse/JDK-8204951
[JDK-8240440] Implement get_safepoint_workers() for parallel GC
https://bugs.openjdk.java.net/browse/JDK-8240440

これらは、JDK 15のParallel GCの、パフォーマンスにフォーカスした興味深い変更でした。ユーザーが直面している変更点をもう一つご紹介したいと思います。

-XX:UseAdaptiveGCBoundary という製品オプションは廃止され、この機能は事実上削除されました。その理由は、この機能は初期バージョンからバグがあり、ランダムなクラッシュの原因となっていたからです。詳細は関連する CSR を参照してください。

[JDK-8242164] Obsolete -XX:UseAdaptiveGCBoundary
https://bugs.openjdk.java.net/browse/JDK-8242164

G1 GC

JDK 14リリース後、Phoronixがチューニング無しでのJDK 8とJDK 14のパフォーマンス比較を投稿しました。その結果を見ると、G1のパフォーマンスがひどく悪そうに見えます。通常、JEP 248でデフォルトのGCがJDK 9でG1に変更されたのが理由と説明されますが、Parallel GCとG1の差が40%ほどとあまりに大きかったため調査したところ、デフォルトのヒープリージョンサイズがこの差の大きな原因だったことがわかりました。これはJDK-8241670で修正されています。同僚のStefanが自身のブログに詳細をまとめています。

OpenJDK 14 Has Some Performance Improvements But OpenJDK 8 Still Strong
https://www.phoronix.com/scan.php?page=article&item=openjdk-14-benchmark
JEP 248: Make G1 the Default Garbage Collector
https://openjdk.java.net/jeps/248
[JDK-8241670] Enhance heap region size ergonomics to improve OOTB performance
https://bugs.openjdk.java.net/browse/JDK-8241670
Improving G1 out-of-the-box performance
https://kstefanj.github.io/2020/04/16/g1-ootb-performance.html
https://logico-jp.io/2020/06/18/improving-g1-out-of-the-box-performance/

いくつかのデータ構造を遅延初期化することでG1の少々起動が改善されました。これにより、起動時間が数ミリ秒短縮されています。

[JDK-8242038] G1: Lazily initialize RSHashTables
https://bugs.openjdk.java.net/browse/JDK-8242038
[JDK-8241920] G1: Lazily initialize OtherRegionsTable::_coarse_map
https://bugs.openjdk.java.net/browse/JDK-8241920
[JDK-8235551] BitMap::count_one_bits should use population_count
https://bugs.openjdk.java.net/browse/JDK-8235551

Other noteworthy changes

この章では、いくつかの小規模な変更や、言及する価値はあるけれどもGCに直接関連しない変更を取り上げます。

Biased locking機能をデフォルトで無効にし、JEP 374で非推奨にしました。Biased lockingは競合しないケース、つまりモニタが単一のスレッドによってのみロックされる場合の同期オーバーヘッドを削減します。基本的に、ひどいプログラムのマルチスレッドアプリケーションのための回避策のように思われます。どうして単一のスレッドによってのみアクセスされるものをロックしたいのでしょうか。しかしその実装がHotSpotのコードを非常に複雑にしており、非推奨、デフォルトで無効化することにより、現実世界のアプリケーションにおけるインパクトのフィードバックを求めています。そのため、顕著なパフォーマンスの劣化に気づいた場合には、このオプションを手動で有効化 (-XX:+UseBiasedLocking) し、結果をご連絡ください。

JEP 374: Disable and Deprecate Biased Locking
https://openjdk.java.net/jeps/374

UseParallelOldGC オプションは廃止され、その機能はJDK 14のJEP 366で非推奨になった後に削除されました。

JEP 366: Deprecate the ParallelScavenge + SerialOld GC Combination
https://openjdk.java.net/jeps/366

What’s next

現在、G1をメモリ消費を減らすための変更、OSへのメモリリリースをより頻繁に行うための変更、およびconcurrent markingに関連する改善を実施しています。ちょっとご紹介しましょう。

これらの変更の1つはJDK-8238687です。この中で、G1は現在のコミットされたヒープサイズを、concurrent marking時やフルGC時のみではなく、GCの都度現在のアプリケーションアクティビティに基づいて動的に調整するようになりました。これにより、G1 のメモリ使用量がよりダイナミックになり、SoftMaxHeapSize の実装も容易になりました。これについては、おそらく別の記事でもう少し詳しく書くことになるでしょう。

[JDK-8238687] Investigate memory uncommit during young collections in G1
https://bugs.openjdk.java.net/browse/JDK-8238687
[JDK-8222145] Add -XX:SoftMaxHeapSize flag
https://bugs.openjdk.java.net/browse/JDK-8222145

Liang Maoが、G1のconcurrent mark cycleを元に戻すことができるようにするための、もう一つの興味深い変更で貢献してくださいました。concurrent markingが設定されている状態で、concurrent markingの最後に一時停止が始まると、G1はconcurrent markingを開始した理由がなくなっていることに気づくかもしれません。この変更により、G1はconcurrent markingを継続せず、以前の一時停止で実行された関連するすべてのセットアップ作業を元に戻すことができるようになりました。これは、特に、頻繁にconcurrent markingを開始する短命の巨大なオブジェクトを多く割り当てるアプリケーションで役立ちますが、高頻度の再回収は、実際にはほとんどの場合、concurrent cycleを必要としない十分なスペースを回収します。この変更は、JDK-8240556を実現するためにもう少し時間が必要でした。

[JDK-8240556] Abort concurrent mark after effective eager reclamation of humongous objects
https://bugs.openjdk.java.net/browse/JDK-8240556

Ziyi Luoが貢献してくださった、G1のアダプティブIHOP (Initiating Heap Occupancy Percent、開始ヒープ占有率) の計算の改良により、マーキングサイクルを開始するタイミングを決定するために短命の巨大オブジェクトを処理する方法が変更されました。以前は、それらは特別な処理はなされなかったため、ほとんどすぐに再利用される巨大なオブジェクトを継続的に割り当てていたアプリケーションは、割り当て率を増加させていました。熱心に(素早く)回収された巨大オブジェクトを処理することで、JDK-8245511で開始されるマーキング開始の閾値が大幅に減少し、その結果、concurrent markingアクティビティが大幅に減少し、パフォーマンスが向上しました。これはすでにJDK 16にプッシュされています。

[JDK-8245511] G1 adaptive IHOP does not account for reclamation of humongous objects by young GC
https://bugs.openjdk.java.net/browse/JDK-8245511

Thanks go to…

もう一つのすばらしいJDKリリースに貢献されたみなさん、次のリリースでお目にかかりましょう。

コメントを残す

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

WordPress.com ロゴ

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

Facebook の写真

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

%s と連携中