メモリー管理
ネイティブ・イメージは、実行時にはJava HotSpot VMではなく、GraalVMに付属のランタイム・システムで実行されます。このランタイムには必要なすべてのコンポーネントが含まれており、そのうちの1つがメモリー管理です。
ネイティブ・イメージによって実行時に割り当てられるJavaオブジェクトは、Javaヒープと呼ばれる領域に存在します。Javaヒープはネイティブ・イメージの起動時に作成され、ネイティブ・イメージの実行中にサイズが増減する可能性があります。ヒープがいっぱいになると、ガベージ・コレクションがトリガーされ、使用されなくなったオブジェクトのメモリーが回収されます。
Javaヒープを管理するために、ネイティブ・イメージは様々なガベージ・コレクタ(GC)実装に対応しています:
- シリアルGCは、GraalVM Community EditionとEnterprise Editionの両方のデフォルトGCです。メモリー・フットプリントおよびJavaヒープ・サイズが小さくなるように最適化されています。
- G1 GC (GraalVM Enterprise Editionでのみ使用可能)は、stop-the-world型の一時停止を減らすように最適化されたマルチスレッドGCであり、高いスループットの実現とレイテンシの改善に貢献します。G1を有効にするには、イメージのビルド時にオプション
--gc=G1
を指定します。現在、G1は、Linux for AMD64でビルドされたネイティブ・イメージでのみ使用できます。 - Epsilon GC (GraalVM 21.2以降で使用可能)は、ガベージ・コレクションを行わないために、割り当てられたメモリーを解放することがないno-opガベージ・コレクタです。このGCの主なユース・ケースは、少量のメモリーのみを割り当てる、実行時間が非常に短いアプリケーションです。Epsilon GCを有効にするには、イメージ・ビルド時にオプション
--gc=epsilon
を指定します。
パフォーマンスに関する考慮事項
ガベージ・コレクションの主なメトリックは、スループット、レイテンシおよびフットプリントです:
- スループットは、長期間で見たときの、ガベージ・コレクション以外の処理に使用される時間の割合です。
- レイテンシはアプリケーションの応答性です。ガベージ・コレクションの一時停止は、応答性に悪影響を及ぼします。
- フットプリントは、プロセスの作業セットをページ数やキャッシュ・ライン数で測定したものです。
Javaヒープの設定の選択は、常にこれらのメトリック間のトレードオフです。たとえば、若い世代を非常に大きくすると、スループットが最大になりますが、フットプリントとレイテンシが犠牲になります。若い世代を小さくすると、若い世代の一時停止を最小にできますが、スループットが犠牲になります。
デフォルトでは、次に示すJavaヒープ設定の値がネイティブ・イメージによって自動的に決定されます。正確な値は、システム構成および使用されるGCによって異なる場合があります。
- 最大Javaヒープ・サイズ: Javaヒープ全体のサイズの上限を定義します。Javaヒープがいっぱいで、GCがJavaオブジェクトの割当てに十分なメモリーを回収できない場合、割当ては
OutOfMemoryError
で失敗します。ノート: ネイティブ・イメージでは、スレッド・スタック、Just-in-Timeのコンパイル済コード、内部データ構造などの一部のデータがJavaヒープとは別のメモリーに配置されるため、最大ヒープ・サイズはJavaヒープの上限にすぎず、必ずしも消費されるメモリーの合計容量の上限ではありません。 - 最小Javaヒープ・サイズ: 実際に使用されるメモリー容量に関係なく、Javaヒープ用に予約されているものとしてGCで常に想定されるメモリー容量を定義します。
- 若い世代のサイズ: ガベージ・コレクションをトリガーせずに割り当てることができるJavaメモリー容量を決定します。
シリアル・ガベージ・コレクタ
シリアルGCは、フットプリントおよびJavaヒープ・サイズが小さくなるように最適化されています。他のGCが指定されていない場合は、GraalVM Community EditionとEnterprise Editionの両方でシリアルGCが暗黙的にデフォルトとして使用されます。オプション--gc=serial
をネイティブ・イメージ・ビルダーに渡すことで、シリアルGCを明示的に有効にすることもできます。
# Build a native image that uses the serial GC with default settings
native-image --gc=serial HelloWorld
概要
そのコアでは、シリアルGCは停止とコピーを行う単純なGC (非パラレル、非同時実行)です。Javaヒープは若い世代と古い世代に分割されます。各世代は均等にサイズ設定されたチャンクのセットで構成され、各チャンクは連続した範囲の仮想メモリーを示します。これらのチャンクは、GC内部でメモリー割当ておよびメモリー回収に使用される単位です。
若い世代は、最近作成されたオブジェクトを含み、edenリージョンとsurvivorリージョンに分割されます。新しいオブジェクトはedenリージョンに割り当てられます。このリージョンが一杯になると、若いコレクションがトリガーされます。edenリージョンで有効なオブジェクトはsurvivorリージョンに移されます。また、survivorリージョン内の有効なオブジェクトはそのリージョンにとどまり、特定の経過期間に達すると(特定の回数のコレクションを経ても有効であると)、その時点で古い世代に移されます。古い世代が一杯になると、若い世代と古い世代両方の未使用オブジェクトの領域を再利用する完全なコレクションがトリガーされます。通常、若いコレクションは完全なコレクションよりはるかに高速ですが、メモリー・フットプリントを小さく保つためには完全なコレクションを実行することが重要です。デフォルトでは、シリアルGCは、高いスループットが提供される世代のサイズを把握しようとしますが、サイズを増やしても効果が低い場合にはさらにサイズを増やそうとはしません。また、フットプリントを小さく保つために、若いコレクションと完全なコレクションにかかった時間の比率を維持しようとします。
最大Javaヒープ・サイズが指定されていない場合、シリアルGCを使用するネイティブ・イメージでは、最大Javaヒープ・サイズが物理メモリー・サイズの80%に設定されます。たとえば、RAMが4GBのマシンでは、最大Javaヒープ・サイズは3.2GBに設定されます。RAMが32GBのマシンで同じイメージが実行される場合、最大Javaヒープ・サイズは25.6GBに設定されます。これはあくまで最大値にすぎないことに注意してください。アプリケーションによっては、実際に使用されるJavaヒープ・メモリー容量が大幅に少なくなる場合があります。このデフォルトの動作をオーバーライドするには、-XX:MaximumHeapSizePercent
の値を指定するか、最大Javaヒープ・サイズを明示的に設定します。
リリース21.3までのGraalVMでは、シリアルGCに対して別の構成を使用しています。survivorリージョンはなく、若い世代は256MBに制限され、デフォルトのコレクション・ポリシーによって若いコレクションと古いコレクションにかかる時間のバランスが調整されます。この構成は、-H:InitialCollectionPolicy=BySpaceAndTime
を使用すると有効にできます
GCがガベージ・コレクションを実行する際に余分なメモリーが必要になることに注意してください(最悪の場合で最大ヒープ・サイズの2倍ですが、通常は大幅に少なくてすみます)。このため、常駐セット・サイズ(RSS)が、ガベージ・コレクション中に一時的に増加することがあります。これは、メモリー制約のある環境(コンテナなど)で問題になる可能性があります。
パフォーマンス・チューニング
GCパフォーマンスおよびメモリー・フットプリントのチューニングには、次のオプションを使用できます:
-XX:MaximumHeapSizePercent
- 最大Javaヒープ・サイズが指定されていない場合に、最大Javaヒープ・サイズとして使用される物理メモリー・サイズの割合(%)です。-XX:MaximumYoungGenerationSizePercent
- 最大Javaヒープ・サイズに対する割合としての若い世代の最大サイズです。-XX:±CollectYoungGenerationSeparately
(GraalVM 21.0以降) - 完全なGCにより若い世代を個別に収集するか、古い世代と一緒に収集するかを決定します。有効にすると、完全なGCの処理でメモリー・フットプリントが減少する可能性があります。ただし、完全なGCで費やされる時間が増える可能性があります。-XX:MaxHeapFree
(GraalVM 21.3以降) - 空きメモリー・チャンクの最大合計サイズ(バイト)。これは、コレクション後に割当て用として残されるため、オペレーティング・システムには返されません。-H:AlignedHeapChunkSize
(イメージのビルド時にのみ指定可能) - ヒープ・チャンクのサイズ(バイト数)です。-H:MaxSurvivorSpaces
(GraalVM 21.1以降、イメージのビルド時のみ指定可能) - 若い世代のために使用されるsurvivor領域の数。すなわち、1つのオブジェクトが古い世代に昇格される際の最長経過期間。値が0の場合、若いコレクションに存続するオブジェクトは古い世代に直接昇格されます。-H:LargeArrayThreshold
(イメージのビルド時にのみ指定可能) - 配列が独自のヒープ・チャンクに割り当てられるようになる最小サイズです。配列が大きいとみなされると割当てコストは高くなりますが、それらがGCによってコピーされることはありません。これにより、GCオーバーヘッドを削減できます。
# Build and execute a native image that uses a maximum heap size of 25% of the physical memory
native-image --gc=serial -R:MaximumHeapSizePercent=25 HelloWorld
./helloworld
# Execute the native image from above but increase the maximum heap size to 75% of the physical memory
./helloworld -XX:MaximumHeapSizePercent=75
次のオプションは、-H:InitialCollectionPolicy=BySpaceAndTime
でのみ使用できます:
-XX:PercentTimeInIncrementalCollection
- GCで若いコレクションの実行に費やされる時間を決定します。デフォルト値の50では、GCにより、若いコレクションと完全なコレクションに費やされる時間のバランス調整が図られます。この値を大きくすると、完全なGCの数が減り、パフォーマンスは向上しますが、メモリー・フットプリントが悪化する可能性があります。この値を小さくすると、完全なGCの数が増え、メモリー・フットプリントは向上しますが、パフォーマンスが低下する可能性があります。
G1ガベージ・コレクタ
GraalVM Enterprise Editionには、Java HotSpot VMのG1 GCに基づくガベージファースト(G1)ガベージ・コレクタも用意されています。現在、G1は、Linux for AMD64でビルドされたネイティブ・イメージでのみ使用できます。これを有効にするには、ネイティブ・イメージ・ビルダーにオプション--gc=G1
を渡します。
# Build a native image that uses the G1 GC with default settings
native-image --gc=G1 HelloWorld
ノート: GraalVM 20.0、20.1および20.2では、G1 GCは低レイテンシGCと呼ばれ、試験段階のオプション-H:+UseLowLatencyGC
を使用して有効にしていました。
概要
G1は、世代別、増分的、パラレル、モーストリ・コンカレント、stop-the-world型および退避型を特徴とするGCです。これは、レイテンシとスループットの間のバランスを最適化することを目的としています。
一部の操作は常にstop-the-world型の一時停止で実行され、スループットを向上させます。グローバル・マーキングのようなヒープ全体の操作など、アプリケーションを停止すると時間がかかる他の操作は、アプリケーションと同時にパラレルに実行されます。G1 GCでは、所定の停止時間ターゲットを長期間にわたって高い確率で満たすよう試みられます。ただし、指定された停止時間が絶対確実なものになるとはかぎりません。
G1では、ヒープは均等サイズのヒープ・リージョンのセットに分割され、各リージョンは連続した範囲の仮想メモリーを示します。リージョンは、GC内部でメモリー割当ておよびメモリー回収に使用される単位です。常に、これらの各リージョンは空であるか、特定の世代に割り当てることができます。
最大Javaヒープ・サイズが指定されていない場合、G1 GCを使用するネイティブ・イメージでは、最大Javaヒープ・サイズが物理メモリー・サイズの25%に設定されます。たとえば、RAMが4GBのマシンでは、最大Javaヒープ・サイズは1GBに設定されます。RAMが32GBのマシンで同じイメージが実行される場合、最大Javaヒープ・サイズは8GBに設定されます。このデフォルトの動作をオーバーライドするには、-XX:MaxRAMPercentage
の値を指定するか、最大Javaヒープ・サイズを明示的に設定します。
パフォーマンス・チューニング
G1 GCは適応型ガベージ・コレクタであり、デフォルト値を変更せずにそのまま使用して効果的に動作できます。ただし、特定のアプリケーションのパフォーマンス・ニーズにあわせてチューニングできます。パフォーマンス・チューニングの実行時に指定できるオプションのごく一部を次に示します:
-H:G1HeapRegionSize
(イメージのビルド時にのみ指定可能) - G1リージョンのサイズです。-XX:MaxRAMPercentage
- 最大ヒープ・サイズが指定されていない場合に、最大ヒープ・サイズとして使用される物理メモリー・サイズの割合(%)です。-XX:MaxGCPauseMillis
- 最大一時停止時間の目標です。-XX:ParallelGCThreads
- ガベージ・コレクションの一時停止時にパラレルな処理に使用されるスレッドの最大数です。-XX:ConcGCThreads
- 同時実行の処理に使用されるスレッドの最大数です。-XX:InitiatingHeapOccupancyPercent
- マーキング・サイクルをトリガーするJavaヒープ占有率のしきい値です。-XX:G1HeapWastePercent
- コレクション・セットの候補内の回収不可領域の許容量です。コレクション・セットの候補内の空き領域がこれより低い場合、G1は領域回収フェーズを停止します。
# Build and execute a native image that uses the G1 GC with a region size of 2MB and a maximum pause time goal of 100ms
native-image --gc=G1 -H:G1RegionSize=2m -R:MaxGCPauseMillis=100 HelloWorld
./helloworld
# Execute the native image from above and override the maximum pause time goal
./helloworld -XX:MaxGCPauseMillis=50
メモリー管理オプション
この項では、使用されるGCに依存しない最も重要なメモリー管理コマンドライン・オプションについて説明します。すべての数値で、接尾辞k
、m
またはg
をスケーリングに使用できます。ネイティブ・イメージ・ビルダーのその他のオプションは、native-image --expert-options-all
を使用してリストできます。
Javaヒープ・サイズ
ネイティブ・イメージを実行すると、システム構成および使用されるGCに基づいて適切なJavaヒープ設定が自動的に決定されます。この自動メカニズムをオーバーライドし、実行時にヒープ・サイズを明示的に設定するには、次のコマンドライン・オプションを使用できます:
-Xmx
- 最大ヒープ・サイズ(バイト数)-Xms
- 最小ヒープ・サイズ(バイト数)-Xmn
- 若い世代のサイズ(バイト数)
イメージのビルド時にデフォルトのヒープ設定を事前構成することもできます。指定した値は、実行時にデフォルト値として使用されます:
-R:MaxHeapSize
(GraalVM 20.0以降) - 最大ヒープ・サイズ(バイト数)-R:MinHeapSize
(GraalVM 20.0以降) - 最小ヒープ・サイズ(バイト数)-R:MaxNewSize
(GraalVM 20.0以降) - 若い世代のサイズ(バイト数)
# Build a native image with the default heap settings and override the heap settings at run time
native-image HelloWorld
./helloworld -Xms2m -Xmx10m -Xmn1m
# Build a native image and "bake" heap settings into the image. The specified values will be used at run time
native-image -R:MinHeapSize=2m -R:MaxHeapSize=10m -R:MaxNewSize=1m HelloWorld
./helloworld
圧縮参照
GraalVM Enterprise Editionは、64ビットではなく32ビットを使用するJavaオブジェクトの圧縮参照をサポートしています。圧縮参照はデフォルトで有効になっており、メモリー・フットプリントに大きな影響を及ぼす可能性があります。ただし、最大Javaヒープ・サイズは32GBのメモリーに制限されます。32GBを超える容量が必要な場合は、圧縮参照を無効にする必要があります。
-H:±UseCompressedReferences
(イメージのビルド時にのみ指定可能): Javaオブジェクトの参照に64ビットではなく32ビットを使用するかどうかを決定します。
ネイティブ・メモリー
ネイティブ・イメージでは、Javaヒープとは別のメモリーを割り当てることもできます。一般的なユース・ケースの1つとして、ネイティブ・メモリーを直接参照するjava.nio.DirectByteBuffer
があります。
-XX:MaxDirectMemorySize
- 直接バッファ割当ての最大サイズです。
ガベージ・コレクションの出力
ネイティブ・イメージを実行する場合、次のオプションを使用してガベージ・コレクションに関する情報を出力できます。どのデータが詳細に出力されるかは、使用されるGCによって異なります。
-XX:+PrintGC
- すべてのガベージ・コレクションの基本情報を出力します-XX:+VerboseGC
- ガベージ・コレクションの詳細を出力するために追加できます
# Execute a native image and print basic garbage collection information
./helloworld -XX:+PrintGC
# Execute a native image and print detailed garbage collection information
./helloworld -XX:+PrintGC -XX:+VerboseGC