これは、なにをしたくて書いたもの?
Java VMのヒープサイズを指定するオプションといえば、-Xmx
(下限値)と-Xmx
(上限値)だと思います。
これらのオプションは、ヒープサイズを直接指定するものですが、パーセンテージでも指定できる-XX:InitialRAMPercentage
と
-XX:MaxRAMPercentage
を今回は試してみたいと思います。
-XX:InitialRAMPercentage、-XX:MaxRAMPercentage
JDK-8186248で、ヒープサイズを使用可能なメモリーに対するパーセンテージで指定できるようになりました。
[JDK-8186248] Allow more flexibility in selecting Heap % of available RAM - Java Bug System
指定できるのは-XX:InitialRAMPercentage
(初期値のパーセンテージ)、-XX:MaxRAMPercentage
(最大値のパーセンテージ)、
そして-XX:MinRAMPercentage
の3つです。
これはコンテナ環境での対応の一環で、コンテナに割り当てるメモリーの量が設定で簡単に変更できる環境だと、直接値を指定するより
パーセンテージで指定できた方がより便利だからですね。
ちなみに、同様の意味を持つ-XX:InitialRAMFraction
、-XX:MaxRAMFraction
、-XX:MinRAMFraction
もあるのですが、こちらは割合で
指定するものになります。パーセンテージで指定したわかりやすいため、これらのオプションはOpenJDK 10で非推奨になっています。
今後は-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
等を使えばよいでしょう。
なお、Java VMのコンテナ対応は、JDK-8146115を参照することになります。
JavaがDockerコンテナ内でどのようにCPU数、メモリサイズを取得しているのかを調べてみる - CLOVER🍀
コンテナ環境のサポートを有効にするには-XX:+UseContainerSupport
(デフォルト)、無効にするには-XX:-UseContainerSupport
です。
また、Java VMに使えるCPU数は-XX:ActiveProcessorCount
で指定することもできます。
今回は、-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
を使ってJava VMに割り当てるヒープサイズがどう変わっていくか
見ていきたいと思います。コンテナ環境下でも見ていきましょう。
環境
今回の環境は、こちら。
$ java --version openjdk 17.0.7 2023-04-18 OpenJDK Runtime Environment (build 17.0.7+7-Ubuntu-0ubuntu122.04.2) OpenJDK 64-Bit Server VM (build 17.0.7+7-Ubuntu-0ubuntu122.04.2, mixed mode, sharing)
Docker。
$ docker version Client: Docker Engine - Community Version: 24.0.4 API version: 1.43 Go version: go1.20.5 Git commit: 3713ee1 Built: Fri Jul 7 14:50:55 2023 OS/Arch: linux/amd64 Context: default Server: Docker Engine - Community Engine: Version: 24.0.4 API version: 1.43 (minimum version 1.12) Go version: go1.20.5 Git commit: 4ffc614 Built: Fri Jul 7 14:50:55 2023 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.6.21 GitCommit: 3dce8eb055cbb6872793272b4f20ed16117344f8 runc: Version: 1.1.7 GitCommit: v1.1.7-0-g860f061 docker-init: Version: 0.19.0 GitCommit: de40ad0
コンテナイメージを使う時は、Eclipse Temurinを使いたいと思います。
サンプルプログラム
ヒープサイズの指定方法を確認するということで、その結果を確認する必要があります。
今回は、MemoryMXBean
と-XX:+PrintFlagsFinal
オプションを使って確認していきたいと思います。
MemoryMXBean (Java SE 17 & JDK 17)
こんなプログラムを用意しました。
PrintHeapSize.java
import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import com.sun.management.OperatingSystemMXBean; public class PrintHeapSize { public static void main(String... args) { MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage(); OperatingSystemMXBean operatingSystemMXBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); System.out.printf( "Heap init = %dMB, used = %dMB, max = %dMB, Physical Memory = %dMB%n", toMB(heapMemoryUsage.getInit()), toMB(heapMemoryUsage.getUsed()), toMB(heapMemoryUsage.getMax()), toMB(operatingSystemMXBean.getTotalMemorySize()) ); } private static long toMB(long value) { return value / 1024 / 1024; } }
まずはデフォルト値を確認する
まずは、デフォルト値を確認してみます。実行時の環境のメモリーは、約16Gです。
実行。
$ java PrintHeapSize.java Heap init = 250MB, used = 21MB, max = 3976MB, Physical Memory = 15900MB
初期ヒープサイズが250MB、最大ヒープサイズが約4GBです。
どうしてこうなるかというと、Java VMのエルゴノミクスからですね。
HotSpot仮想マシン・ガベージ・コレクション・チューニング・ガイド / エルゴノミクス / ガベージ・コレクタ、ヒープおよびランタイム・コンパイラのデフォルト選択
メモリーが約16GBなので、その1/64で250MB、1/4で約4GBですね。
-XX:+PrintFlagsFinal
オプションで、各値を見てみます。
$ java -XX:+PrintFlagsFinal --version | grep -E '(Max|Min|Initial)HeapSize|RAM(Percentage|Fraction)' size_t InitialHeapSize = 262144000 {product} {ergonomic} uintx InitialRAMFraction = 64 {product} {default} double InitialRAMPercentage = 1.562500 {product} {default} size_t MaxHeapSize = 4169138176 {product} {ergonomic} uintx MaxRAMFraction = 4 {product} {default} double MaxRAMPercentage = 25.000000 {product} {default} size_t MinHeapSize = 8388608 {product} {ergonomic} uintx MinRAMFraction = 2 {product} {default} double MinRAMPercentage = 50.000000 {product} {default} size_t SoftMaxHeapSize = 4169138176 {manageable} {ergonomic}
こんな感じです。初期ヒープサイズ、最大ヒープサイズはエルゴノミクス決定していることがわかりますね。
なお、-XX:InitialRAMPercentage
の初期値は1.5625、-XX:MaxRAMPercentage
の初期値は25、-XX:MinRAMPercentage
の初期値は50
みたいです。
double
なので、小数での指定が可能ですね
1.5625は1/64、25は1/4なのでエルゴノミクスとも合った値になっているようです。
-Xms、-Xmxを指定してみる
次に-Xms
、-Xmx
オプションを指定して実行してみます。
$ java -Xms1G -Xmx8G PrintHeapSize.java Heap init = 1024MB, used = 23MB, max = 8192MB, Physical Memory = 15900MB
当然ですが、最小ヒープサイズ、最大ヒープサイズが変わりました。
-XX:+PrintFlagsFinal
オプションで確認。
$ java -Xms1G -Xmx8G -XX:+PrintFlagsFinal --version | grep -E '(Max|Min|Initial)HeapSize|RAM(Percentage|Fraction)' size_t InitialHeapSize = 1073741824 {product} {command line} uintx InitialRAMFraction = 64 {product} {default} double InitialRAMPercentage = 1.562500 {product} {default} size_t MaxHeapSize = 8589934592 {product} {command line} uintx MaxRAMFraction = 4 {product} {default} double MaxRAMPercentage = 25.000000 {product} {default} size_t MinHeapSize = 1073741824 {product} {command line} uintx MinRAMFraction = 2 {product} {default} double MinRAMPercentage = 50.000000 {product} {default} size_t SoftMaxHeapSize = 8589934592 {manageable} {ergonomic}
InitialHeapSize
とMaxHeapSize
が変わりましたね。
-XX:InitialRAMPercentage、-XX:MaxRAMPercentageを指定してみる
次に、-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
を指定してみましょう。
$ java -XX:InitialRAMPercentage=6.25 -XX:MaxRAMPercentage=50 PrintHeapSize.java Heap init = 996MB, used = 23MB, max = 7952MB, Physical Memory = 15900MB
ざっくり、先ほどの-Xms`、
-Xmx`に近い値になるパーセンテージにしてみました。ちょっと足りませんが。
この結果を見ると、-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
のコントロール対象は-Xms
、-Xmx
と一致することが
わかりますね。
この時、-XX:+PrintFlagsFinal
で値を確認してみます。
$ java -XX:InitialRAMPercentage=6.25 -XX:MaxRAMPercentage=50 -XX:+PrintFlagsFinal --version | grep -E '(Max|Min|Initial)HeapSize|RAM(Percentage|Fraction)' size_t InitialHeapSize = 1044381696 {product} {ergonomic} uintx InitialRAMFraction = 64 {product} {default} double InitialRAMPercentage = 6.250000 {product} {command line} size_t MaxHeapSize = 8338276352 {product} {ergonomic} uintx MaxRAMFraction = 4 {product} {default} double MaxRAMPercentage = 50.000000 {product} {command line} size_t MinHeapSize = 8388608 {product} {ergonomic} uintx MinRAMFraction = 2 {product} {default} double MinRAMPercentage = 50.000000 {product} {default} size_t SoftMaxHeapSize = 8338276352 {manageable} {ergonomic}
よく見ると、InitialHeapSize
とMaxHeapSize
の値が変動しています。-Xms`、
-Xmxを指定しても
-XX:InitialRAMPercentage、
-XX:MaxRAMPercentage`には影響していませんでしたが、
ヒープサイズの値には反映してくれるんですね。
なお、InitialRAMFraction
とMaxRAMFraction
は変わっていません。
-XX:InitialRAMFraction、-XX:MaxRAMFractionも試してみる
ついでに、-XX:InitialRAMFraction
、-XX:MaxRAMFraction
も試してみましょう。こちらは「割合」で指定します。
$ java -XX:InitialRAMFraction=16 -XX:MaxRAMFraction=2 PrintHeapSize.java OpenJDK 64-Bit Server VM warning: Option InitialRAMFraction was deprecated in version 10.0 and will likely be removed in a future release. OpenJDK 64-Bit Server VM warning: Option MaxRAMFraction was deprecated in version 10.0 and will likely be removed in a future release. Heap init = 996MB, used = 23MB, max = 7952MB, Physical Memory = 15900MB
-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
を指定した時と、同じになる割合を指定しました。
思いきり非推奨警告が出ていますね。
-XX:+PrintFlagsFinal
で見てみます。
$ java -XX:InitialRAMFraction=16 -XX:MaxRAMFraction=2 -XX:+PrintFlagsFinal --version | grep -E '(Max|Min|Initial)HeapSize|RAM(Percentage|Fraction)' OpenJDK 64-Bit Server VM warning: Option InitialRAMFraction was deprecated in version 10.0 and will likely be removed in a future release. OpenJDK 64-Bit Server VM warning: Option MaxRAMFraction was deprecated in version 10.0 and will likely be removed in a future release. size_t InitialHeapSize = 1044381696 {product} {ergonomic} uintx InitialRAMFraction = 16 {product} {command line} double InitialRAMPercentage = 6.250000 {product} {default} size_t MaxHeapSize = 8338276352 {product} {ergonomic} uintx MaxRAMFraction = 2 {product} {command line} double MaxRAMPercentage = 50.000000 {product} {default} size_t MinHeapSize = 8388608 {product} {ergonomic} uintx MinRAMFraction = 2 {product} {default} double MinRAMPercentage = 50.000000 {product} {default} size_t SoftMaxHeapSize = 8338276352 {manageable} {ergonomic}
こちらもInitialHeapSize
とMaxHeapSize
の値に反映されるようです。InitialRAMPercentage
とMaxRAMPercentage
は
変わっていませんが。
コンテナ環境で試してみる
最後に、コンテナ環境で試してみましょう。
先ほどのプログラムを、コンテナ内で実行。
$ docker container run -it --rm --name temurin \ -v $(pwd):/host \ eclipse-temurin:17.0.7_7-jdk-jammy java /host/PrintHeapSize.java Heap init = 250MB, used = 20MB, max = 3976MB, Physical Memory = 15900MB
結果は、ホスト側で実行した時と同じです。リソースを絞っていませんからね。
コンテナに割り当てるメモリーを2Gにしてみましょう。
$ docker container run -it --rm --name temurin \ -v $(pwd):/host \ --memory 2G \ eclipse-temurin:17.0.7_7-jdk-jammy java /host/PrintHeapSize.java Heap init = 32MB, used = 9MB, max = 512MB, Physical Memory = 2048MB
コンテナに割り当てたメモリーサイズを認識していますね。
では、-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
を指定してみます。
docker container run -it --rm --name temurin \ -v $(pwd):/host \ --memory 2G \ eclipse-temurin:17.0.7_7-jdk-jammy java -XX:InitialRAMPercentage=6.25 -XX:MaxRAMPercentage=50 /host/PrintHeapSize.java Heap init = 128MB, used = 19MB, max = 1024MB, Physical Memory = 2048MB
コンテナに割り当てたメモリーサイズを元に、パーセンテージで指定した値にヒープサイズが設定されましたね。
これで、確認したいことはできた感じです。
まとめ
Java VMのヒープサイズをパーセンテージで指定できる、-XX:InitialRAMPercentage
、-XX:MaxRAMPercentage
を試してみました。
コンテナ環境だとヒープサイズをパーセンテージ(割合)で指定したくなることも多いと思いますので、覚えておきましょう。
参考)