G1GCとShenandoahGCのコミット済メモリ解放

Java11で動作している環境にて、JVMが使用しているメモリは下がっているのに確保しているヒープメモリがOSに解放されないことがあった。

Currently the G1 garbage collector may not return committed Java heap memory to the operating system in a timely manner. G1 only returns memory from the Java heap at either a full GC or during a concurrent cycle. Since G1 tries hard to completely avoid full GCs, and only triggers a concurrent cycle based on Java heap occupancy and allocation activity, it will not return Java heap memory in many cases unless forced to do so externally.

https://openjdk.org/jeps/346

OpenJDKの公式ページを見ると、GCでデフォルトのG1GCを使用している場合、確保されたヒープメモリをOSにタイムリーに返さないことがあるらしい。
また、FullGCの発生やconcurrent cycleをきっかけにメモリをOSに解放するらしいが、基本的には外部から強制されない限りヒープメモリは返されないらしい。

Shenandoah and OpenJ9’s GenCon collector already provide similar functionality

https://openjdk.org/jeps/346

どうやらShenandoahGCでは確保したヒープメモリをOSに解放するようになっているらしいので、
メモリの挙動について、G1GC、ShenandoahGCでちょっと動かして見てみた。

環境

  • Windows11
  • tomcat-9.0.70
  • openjdk 11.0.17

tomcatを起動する際は特にJVM起動時引数などは変えず、GCの変更以外は特に触れない。

G1GC

試してみた環境だとデフォルトでTomcatが確保する最大ヒープは約4G。
適当なJavaのWebアプリケーションを作って、じわじわメモリを使う処理を動かした。

下の図のように、じわじわ使用済ヒープメモリが増加し、あるタイミングでGCが動いたのか使用済ヒープメモリががくっと下がったことがわかる。
また、使用済のメモリは1Gまで下がったのに、コミット済メモリが解放されておらず、4G近く確保され続けている。

さらに1時間ほど放置してみたのだが、特にコミット済メモリは解放されず、確保されたままになっている。
公式に書いてあったように、FullGCなどが発生しない限り解放されないみたい。

ShenandoahGC

TomcatのJVM起動時引数に「-XX:+UseShenandoahGC」を追加し、ShenandoahGCを有効にして確認。

G1GCの時と同様にじわじわ使用済メモリが上がった後に、GCが発生したのか使用済メモリががくっと1Gほどまで下がったが、コミット済メモリは3Gほど取られたままの状態。

5分ほど放置したところ、下の図のように使用済メモリと合わせてコミット済メモリも解放されて1Gほどにまで下がったことを確認。
ただ、使用済みは46Mくらいまで下がったのに、コミット済は多くとられたままなのでさらに放置してみる。

さらに5分ほど放置したところ、コミット済メモリが68Mほどにまで下がり、使用済とコミット済メモリの大きな差はなくなったように見える。

結果

G1GCではコミット済メモリが解放されず、使用済とコミット済メモリでかなりの差が発生したが、ShenandoahGCでは5分間隔でコミット済メモリが解放されていることが分かった。
どうやらShenandoahGCはデフォルトとして5分で不要なコミット済メモリをOSに解放してくれるらしい。調整が入るこの時間をJVM起動時引数の「-XX:ShenandoahUncommitDelay」で設定することができるみたい。

Shenandoah does provide similar functionality under experimental flags:
-XX:+ShenandoahUncommit
-XX:ShenandoahUncommitDelay
Which are also enabled by default:
bool ShenandoahUncommit = true {experimental} {default}
uintx ShenandoahUncommitDelay = 300000 {experimental} {default}
bool ShenandoahUncommitWithIdle = false {experimental} {default}

https://mail.openjdk.org/pipermail/hotspot-gc-dev/2018-June/022205.html

-XX:ShenandoahUncommitDelay=1000 – garbage collector will start uncommitting memory that was not used for more than this time (in milliseconds). Note that making the delay too low may introduce allocation stalls when application would like to get the memory back. In real world deployments, making the delay larger than 1 second is recommended

https://www.javacodegeeks.com/2017/11/minimize-java-memory-usage-right-garbage-collector.html

また、他にもOpenJDKのQuestionや海外の記事などを調べてみたのだが、ShenandoahGCはFullGCを発生させずにコミット済メモリを開放しているとのこと。

It does not need full gc before uncommitting

https://mail.openjdk.org/pipermail/hotspot-gc-dev/2018-June/022205.html

The main difference compared to others is the ability to shrink (uncommit and release unused RAM to OS) asynchronously without necessity to call Full GC. Shenandoah can compact live objects, clean garbage and release RAM back to OS almost immediately after detecting free memory. And the possibility of omitting Full GC leads to eliminating related performance degradation.

https://www.javacodegeeks.com/2017/11/minimize-java-memory-usage-right-garbage-collector.html
タイトルとURLをコピーしました