メモリー管理

ネイティブ・イメージは、実行時にはJava HotSpot VMではなく、GraalVMに付属のランタイム・システムで実行されます。このランタイムには必要なすべてのコンポーネントが含まれており、そのうちの1つがメモリー管理です。

ネイティブ・イメージによって実行時に割り当てられるJavaオブジェクトは、Javaヒープと呼ばれる領域に存在します。Javaヒープはネイティブ・イメージの起動時に作成され、ネイティブ・イメージの実行中にサイズが増減する可能性があります。ヒープがいっぱいになると、ガベージ・コレクションがトリガーされ、使用されなくなったオブジェクトのメモリーが回収されます。

Javaヒープを管理するために、ネイティブ・イメージは様々なガベージ・コレクタ(GC)実装に対応しています:

パフォーマンスに関する考慮事項

ガベージ・コレクションの主なメトリックは、スループット、レイテンシおよびフットプリントです:

Javaヒープの設定の選択は、常にこれらのメトリック間のトレードオフです。たとえば、若い世代を非常に大きくすると、スループットが最大になりますが、フットプリントとレイテンシが犠牲になります。若い世代を小さくすると、若い世代の一時停止を最小にできますが、スループットが犠牲になります。

デフォルトでは、次に示すJavaヒープ設定の値がネイティブ・イメージによって自動的に決定されます。正確な値は、システム構成および使用されるGCによって異なる場合があります。

シリアル・ガベージ・コレクタ

シリアルGCは、フットプリントおよびJavaヒープ・サイズが小さくなるように最適化されています。他のGCが指定されていない場合は、GraalVMのデフォルトであるシリアル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パフォーマンスおよびメモリー・フットプリントのチューニングには、次のオプションを使用できます:

# 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でのみ使用できます:

G1ガベージ・コレクタ

Oracle GraalVMには、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は適応型ガベージ・コレクタであり、デフォルト値を変更せずにそのまま使用して効果的に動作できます。ただし、特定のアプリケーションのパフォーマンス・ニーズにあわせてチューニングできます。パフォーマンス・チューニングの実行時に指定できるオプションのごく一部を次に示します:

# 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に依存しない最も重要なメモリー管理コマンドライン・オプションについて説明します。すべての数値で、接尾辞kmまたはgをスケーリングに使用できます。ネイティブ・イメージ・ビルダーのその他のオプションは、native-image --expert-options-allを使用してリストできます。

Javaヒープ・サイズ

ネイティブ・イメージを実行すると、システム構成および使用されるGCに基づいて適切なJavaヒープ設定が自動的に決定されます。この自動メカニズムをオーバーライドし、実行時にヒープ・サイズを明示的に設定するには、次のコマンドライン・オプションを使用できます:

イメージのビルド時にデフォルトのヒープ設定を事前構成することもできます。指定した値は、実行時にデフォルト値として使用されます:

# 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

圧縮参照

Oracle GraalVMは、64ビットではなく32ビットを使用するJavaオブジェクトへの圧縮参照をサポートしています。圧縮参照はデフォルトで有効になっており、メモリー・フットプリントに大きな影響を及ぼす可能性があります。ただし、最大Javaヒープ・サイズは32GBのメモリーに制限されます。32GBを超える容量が必要な場合は、圧縮参照を無効にする必要があります。

ネイティブ・メモリー

ネイティブ・イメージでは、Javaヒープとは別のメモリーを割り当てることもできます。一般的なユース・ケースの1つとして、ネイティブ・メモリーを直接参照するjava.nio.DirectByteBufferがあります。

ガベージ・コレクションの出力

ネイティブ・イメージを実行する場合、次のオプションを使用してガベージ・コレクションに関する情報を出力できます。どのデータが詳細に出力されるかは、使用されるGCによって異なります。

# 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

その他の情報