kagamihogeの日記

kagamihogeの日記です。

JEP 346: Promptly Return Unused Committed Memory from G1をテキトーに訳した

http://openjdk.java.net/jeps/346

JEP 346: Promptly Return Unused Committed Memory from G1

Authors  Rodrigo Bruno, Thomas Schatzl, Ruslan Synytsky
Owner   Thomas Schatzl
Type    Feature
Scope   Implementation
Status  Proposed to Target
Release 12
Component   hotspot/gc
Discussion  hotspot dash gc dash dev at openjdk dot java dot net
Effort  M
Duration    S
Reviewed by Mikael Vidstedt, Stefan Johansson
Endorsed by Vladimir Kozlov
Created 2018/05/30 14:23
Updated 2018/11/29 22:49
Issue   8204089

Summary

アイドル時にOSへJavaヒープメモリを自動返却するようにG1 GCを拡張します。

Non-Goals

  • コミット済みで空のページをJavaプロセス間で共有する。メモリがOSに返却(未コミット)される。
  • メモリ返却処理がCPUリソースを節約する必要は無く、瞬時に終わる必要も無い。
  • 利用可能な未コミットメモリ以外のメモリを返却するのに別のメソッドを使用する。
  • G1以外のコレクターのサポート。

Success Metrics

アプリケーションがほぼ活動していない状況で、G1が妥当な時間内に未使用Javaヒープメモリを解放する。

Motivation

現行のG1 GCはOSにコミット済Javaヒープメモリをタイムリーに返さない場合があります。G1はfull GCかconcurrent cycleのどちらかでだけJavaヒープからメモリを返却します。G1はfull GCを完全回避する努力をし、concurrent cycleはJavaヒープ占有率とアロケーションアクティビティだけをベースにトリガされるので、外部から強制的に行わない限り大半のケースでJavaヒープメモリを返却しません。

この振る舞いが特に欠点となるのはリソース使用分に課金されるコンテナ環境です。VMが不活性中で割り当てメモリリソースの一部のみしか使用してない状況であっても、G1はすべてのJavaヒープを保持します。その結果として常に全リソースに対する支払をすることになり、また、クラウドプロバイダーでそのようなハードウェアを完全に用意することは出来ません

仮に、VMJavaヒープが不使用(アイドル)状態を検出可能で、自動的にその間はヒープの使用を減少させれば、クラウド提供者・使用者双方にメリットがあります。

ShenandoahおよびGenCon collectorが既に類似の機能を提供しています。

Bruno et al., section 5.5でHTTPリクエストを提供するTomcatサーバの実際の使用を基にしたプロトタイプテストを公開しています。日中は普通に使用して夜間の大部分はアイドルするもので、Java VMのコミット済みメモリ総量の85%を減少可能としています。

Description

OSに最大限のメモリを返却するため、アプリケーションが非アクティブのとき、G1の継続を定期的に試行するか、Javaヒープの使用を全体的に確認するconcurrent cycleを試行します。これにより、Javaヒープの未使用部分がOSに自動返却されます。または、ユーザが制御する場合、フルGCでメモリを最大限返却できます。

アプリケーションが非アクティブと見なされると、G1は以下を両方満たす場合にperiodic garbage collectionをトリガします。

  • 以前に何らかのGCが停止してG1PeriodicGCIntervalミリ秒よりも経過し、かつ、その時点でconcurrent cycleが実行中では無い場合。ゼロは即時メモリ回収のperiodic garbage collectionsがdisabledになります。
  • ホストシステムのgetloadavg()が返すロードアベレージG1PeriodicGCSystemLoadThresholdを上回る場合。G1PeriodicGCSystemLoadThresholdがゼロの場合無視される。

上記条件のいずれかを満たさなくなった場合、その時点でのperiodic garbage collectionの予定はキャンセルされます。periodic garbage collectionはG1PeriodicGCInterval経過後に再チェックされます。

periodic garbage collectioのタイプはG1PeriodicGCInvokesConcurrentオプションで決定します。設定している場合、G1は継続するかconcurrent cycleを開始し、設定しない場合、G1はフルGCを実行します。いずれのコレクション終了時にも、G1は現在のJavaヒープサイズを調整し、場合によってはOSにメモリ返却をします。その後のJavaヒープサイズはヒープ設定で決定され、これにはMinHeapFreeRatio, MaxHeapFreeRatio, 最小・最大ヒープサイズ設定などがあります。

デフォルトでは、G1が開始するかperiodic garbage collection中concurrent cycleを継続します。この場合アプリケーションの混乱は最小限ですが、full collectionと比較するとより多くのメモリ返却を結局は出来ない可能性があります。

本機能で起動するgarbage collectionはG1 Periodic Collectionの発生としてタグ付けされます。ログの見え方の例は以下になりまs。

(1) [6.084s][debug][gc,periodic ] Checking for periodic GC.
    [6.086s][info ][gc          ] GC(13) Pause Young (Concurrent Start) (G1 Periodic Collection) 37M->36M(78M) 1.786ms
(2) [9.087s][debug][gc,periodic ] Checking for periodic GC.
    [9.088s][info ][gc          ] GC(15) Pause Young (Prepare Mixed) (G1 Periodic Collection) 9M->9M(32M) 0.722ms
(3) [12.089s][debug][gc,periodic ] Checking for periodic GC.
    [12.091s][info ][gc          ] GC(16) Pause Young (Mixed) (G1 Periodic Collection) 9M->5M(32M) 1.776ms
(4) [15.092s][debug][gc,periodic ] Checking for periodic GC.
    [15.097s][info ][gc          ] GC(17) Pause Young (Mixed) (G1 Periodic Collection) 5M->1M(32M) 4.142ms
(5) [18.098s][debug][gc,periodic ] Checking for periodic GC.
    [18.100s][info ][gc          ] GC(18) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(32M) 1.685ms
(6) [21.101s][debug][gc,periodic ] Checking for periodic GC.
    [21.102s][info ][gc          ] GC(20) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(32M) 0.868ms
(7) [24.104s][debug][gc,periodic ] Checking for periodic GC.
    [24.104s][info ][gc          ] GC(22) Pause Young (Concurrent Start) (G1 Periodic Collection) 1M->1M(32M) 0.778ms

上記例では、3000msのG1PeriodicGCIntervalを実行し、step(1)でG1はconcurrent cycleを初期化しており、(Concurrent Start)(G1 Periodic Collection)がそれを示していて、アプリケーションが何らかで非アクティブになった後にこうなります。このconcurrent cycleは最初にいくらかのメモリを返却しており、(1)から(2)で(78M)から(32M)に減少しています。(2)から(4)でperiodic collectionsが数度起動し、この際ヒープ圧縮のためにmixed collectionを起動します。それ以降のperiodic garbage collectionsの(5)から(7)のconcurrent cycleは、G1がその時点では、mixed GCを開始するold generationでのGCは不要と判断しています。これは、periodic garbage collectionsの(5)から(7)は、既に最小ヒープサイズに達したので、それ以上ヒープを縮小していません。

アプリケーションの非アクティブ期間に生存オブジェクトが変化(例:ソフト参照の期限切れなど)すると、アイドル時にコミット済Javaヒープの更なる縮小が起こります。

Alternatives

同様なことをVM外から実行可能で、例えば、jcmdツールやVMに何らかのコードを追加します。これは隠れコストが発生し、cronライクなチェックを想定すると、例えばあるノードに数百数千コンテナを乗せる場合、ヒープ圧縮が同時に多数のコンテナで実行される可能性があり、ホストに大量のCPUスパイクが発生します。

別案に個々のJavaプロセスに自動アタッチされるJavaエージェントがあります。Then the time of the check is distributed naturally as containers start at different time, 新規プロセスを立ち上げないので高めのCPUコストを減らせます。ただ、このやり方はユーザにかなりの負担を強いるため、採用されるとユーザを失望させるでしょう。

ここで挙げるユースケースで、適時Javaヒープを縮小することは、VMの特別サポートを正当化するほどの極めて一般的なユースケースと見なしています。

Risks and Assumptions

デフォルト設定値では本機能を無効にします。これによりレイテンシやスループットにシビアなアプリケーションにおいてVMの振る舞いに予測不能な振る舞いを無くせます。有効にする場合、通常はOSにJavaヒープを戻すのが望ましく、結果生じるconcurrent cycleやアプリケーションスループットへのインパクトは無視出来ます。

本機能を有効化すると、VMはperiodic collectionsを上述の設定に基づき実行しますが、それ以外のオプションとは無関係です。例えば、ユーザが-Xmsおよび-Xmxとそれ以外のオプション(の組み合わせ)をする場合、VMは最小かつ一貫性のあるGC停止という推定が出来ます。This will not be the case for consistency reasons.

periodic garbage collectionsがプロプラム実行を阻害するケースでは、システム全体CPUを考慮する制御方法か、ユーザで完全にperiodic garbage collectionsを無効化する方法を提供します。