結論: ヒープサイズ4GBのときWindowsで2秒程度、WSLで3秒程度。ヒープサイズ1GBのときWindowsで1秒程度、WSLで0.5秒程度、起動時のオーバーヘッドが増える模様。
動機
JavaVMの起動時に-Xmsと-Xmxに同じ値を指定しても、JavaVM起動時点でそれだけの物理メモリが確保されるわけではない、という話があります。-Xmsと-Xmxでメモリ量を指定してJavaVMを起動した直後の状態だと、おそらくUnix系のOSならbrk(2)かsbrk(2)、WindowsだったらVirtualAllocあたりが呼ばれるので、この時点でプログラムブレークの位置は設定される*1 。だけど、JavaVMは確保したメモリすべてに触りにいくわけじゃないからその時点ではOSはページフォールトを発生させる理由がない。よってOSから見ると仮想メモリがコミットされるだけで物理メモリは割り当られてない、という動きになるわけです。
それだけでもメモリ確保の都度brk(2)やsbrk(2)を呼ぶ動きを抑止できるという意味で、-Xmsと-Xmxを同じ値に設定するのはスループットを高めるには有用と思われます*2。
で、物理メモリ上に本当に領域が確保される必要はないにしても、システムのリソース設計をするときはJavaヒープが全て物理メモリに載った状態でもスワップが発生しないよう設計にしておかないと、スラッシングによる性能劣化が怖い。そういう前提でリソース設計をしてたのに、いざシステムが稼動したら設計をミスっててプロセスが仮想メモリを確保できず起動しない、という状況はテストで潰しておきたい。OSがWindowsであればOSの仕様上オーバーコミットはできないからシステム稼動中と同じようにプロセスを起動しておけばこれがテストできる*3。一方、Linuxの場合はオーバーコミットするのがOSのデフォルト設定になっているので、この目的でテストをしたければ-Xmsと-XmxでコミットしたメモリすべてにJavaVMが触りに行ってページフォールトを発生させないとテストができないわけです。
そのためかどうかわかりませんが、Javaには-XX:+AlwaysPreTouchという起動オプションがあって、これを指定するとJavaVM起動時に確保したメモリ(ページ)すべてに触りに行く。ただ、もちろん全メモリに触りに行くわけなので起動時のオーバーヘッドはそれなりにかかる。じゃぁどれくらいオーバーヘッドがかかるの?というのが知りたいわけです。毎分1回起動してそれなりの時間処理をするようなプロセスで、プロセス起動時のオーバーヘッドが何十秒も増えたらそれは困るので。
以下のようなプログラムを作って、-XX:+AlwaysPreTouchありとなしで差を確認してみました。
import java.lang.management.ManagementFactory; import com.sun.management.OperatingSystemMXBean; public class AlwaysPreTouchTest { public static void main(String[] main) { OperatingSystemMXBean osMBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); System.out.printf("Free physical memory: %dkB\n", osMBean.getFreeMemorySize() / 1024); System.out.printf("Total Java Heap: %dkB\n", Runtime.getRuntime().totalMemory() / 1024); } }
Windowsの場合
Windowsでの結果は以下のような感じ。ヒープ4GBで-XX:+AlwaysPreTouchを指定したときの起動時間は1秒~2秒でけっこうバラつく。
PS C:\tmp> java --version openjdk 17.0.9 2023-10-17 OpenJDK Runtime Environment Temurin-17.0.9+9 (build 17.0.9+9) OpenJDK 64-Bit Server VM Temurin-17.0.9+9 (build 17.0.9+9, mixed mode, sharing) PS C:\tmp> (Measure-Command { Invoke-Expression "java.exe -Xmx4g -Xms4g AlwaysPreTouchTest" | Out-Default }).TotalSeconds Free physical memory: 5545840kB Total Java Heap: 4194304kB 0.3735332 PS C:\tmp> (Measure-Command { Invoke-Expression "java.exe -Xmx4g -Xms4g -XX:+AlwaysPreTouch AlwaysPreTouchTest" | Out-Default }).TotalSeconds Free physical memory: 1208368kB Total Java Heap: 4194304kB 2.4802916 PS C:\tmp> (Measure-Command { Invoke-Expression "java.exe -Xmx1g -Xms1g AlwaysPreTouchTest" | Out-Default }).TotalSeconds Free physical memory: 5530976kB Total Java Heap: 1048576kB 0.1812453 PS C:\tmp> (Measure-Command { Invoke-Expression "java.exe -Xmx1g -Xms1g -XX:+AlwaysPreTouch AlwaysPreTouchTest" | Out-Default }).TotalSeconds Free physical memory: 4451272kB Total Java Heap: 1048576kB 1.2108386
WSLの場合
WSLでの結果は以下のような感じ。ヒープ4GBで-XX:+AlwaysPreTouchを指定したときの起動時間は3秒程度で安定している。一方、ヒープ1GBのときのオーバーヘッドはなぜか生のWindowsより小さい。
satob:~$ java --version openjdk 17.0.11 2024-04-16 OpenJDK Runtime Environment (build 17.0.11+9-Ubuntu-120.04.2) OpenJDK 64-Bit Server VM (build 17.0.11+9-Ubuntu-120.04.2, mixed mode, sharing) satob:~$ time java -Xmx4g -Xms4g AlwaysPreTouchTest Free physical memory: 7498060kB Total Java Heap: 4194304kB real 0m0.091s user 0m0.090s sys 0m0.019s satob:~$ time java -Xmx4g -Xms4g -XX:+AlwaysPreTouch AlwaysPreTouchTest Free physical memory: 3159932kB Total Java Heap: 4194304kB real 0m0.472s user 0m0.201s sys 0m3.046s satob:~$ time java -Xmx1g -Xms1g AlwaysPreTouchTest Free physical memory: 7529068kB Total Java Heap: 1048576kB real 0m0.218s user 0m0.113s sys 0m0.135s satob:~$ time java -Xmx1g -Xms1g -XX:+AlwaysPreTouch AlwaysPreTouchTest Free physical memory: 6441500kB Total Java Heap: 1048576kB real 0m0.184s user 0m0.096s sys 0m0.756s