メモリ管理

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

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

Java ヒープを管理するために、ネイティブイメージはさまざまなガベージコレクタ(GC)の実装を提供します

  • シリアル GC は、GraalVM ネイティブイメージのデフォルトの GC です。メモリフットプリントと小さな Java ヒープサイズに最適化されています。
  • G1 GC は、stop-the-world ポーズを削減してレイテンシーを改善し、同時に高いスループットを実現するように最適化されたマルチスレッド GC です。有効にするには、native-image ビルダーにオプション --gc=G1 を渡します。現在、G1 ガベージコレクタは、Linux AMD64 および AArch64 アーキテクチャ上のネイティブイメージで使用できます。(GraalVM Community Edition では利用できません。)
  • イプシロン GC (GraalVM 21.2 以降で利用可能) は、ガベージコレクションをまったく行わず、割り当てられたメモリを解放しない、no-op ガベージコレクタです。この GC の主な使用ケースは、少量のメモリのみを割り当てる非常に短時間で実行されるアプリケーションです。イプシロン GC を有効にするには、イメージのビルド時にオプション --gc=epsilon を指定します。

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

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

  • スループットとは、長期間にわたって考慮されたガベージコレクションに費やされなかった合計時間の割合です。
  • レイテンシーとは、アプリケーションの応答性です。ガベージコレクションの一時停止は、応答性に悪影響を与えます。
  • フットプリントとは、ページとキャッシュラインで測定されたプロセスのワーキングセットです。

Java ヒープの設定を選択することは、常にこれらのメトリクスの間のトレードオフです。たとえば、非常に大きな young generation はスループットを最大化する可能性がありますが、フットプリントとレイテンシーを犠牲にします。young generation の一時停止は、スループットを犠牲にして小さな young generation を使用することで最小限に抑えることができます。

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

  • 最大 Java ヒープサイズは、Java ヒープ全体のサイズの上限を定義します。Java ヒープが満杯で、GC が Java オブジェクトの割り当てに十分なメモリを再利用できない場合、割り当ては OutOfMemoryError で失敗します。注: 最大ヒープサイズは Java ヒープの上限に過ぎず、必ずしも消費されるメモリの総量の上限ではありません。ネイティブイメージは、スレッドスタック、ジャストインタイムコンパイルされたコード(Truffle ランタイムコンパイルの場合)、および Java ヒープとは別のメモリ内の内部データ構造などの一部のデータを配置するためです。
  • 最小 Java ヒープサイズは、GC が Java ヒープ用に常に予約されていると想定できるメモリ量を定義します。実際に使用されているメモリ量がどれほど少ないかは関係ありません。
  • young generation サイズは、ガベージコレクションをトリガーせずに割り当てることができる Java メモリの量を決定します。

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

シリアル GC は、低フットプリントと小さな Java ヒープサイズに最適化されています。他の GC が指定されていない場合、シリアル GC は GraalVM でデフォルトとして暗黙的に使用されます。ネイティブイメージビルダーにオプション --gc=serial を渡して、シリアル GC を明示的に有効にすることもできます。

# Build a native image that uses the serial GC with default settings
native-image --gc=serial HelloWorld

概要 #

そのコアにおいて、シリアル GC は、単純な(非並列、非同時)停止およびコピー GC です。Java ヒープを young generation と old generation に分割します。各世代は、それぞれが仮想メモリの連続した範囲である、同じサイズのチャンクのセットで構成されています。これらのチャンクは、メモリ割り当てとメモリ再利用のための GC 内部の単位です。

young generation には最近作成されたオブジェクトが含まれており、eden リージョンとサバイバーリージョンに分割されています。新しいオブジェクトは eden リージョンに割り当てられ、このリージョンが満杯になると、若いコレクションがトリガーされます。eden リージョンで生きているオブジェクトはサバイバーリージョンに移動され、サバイバーリージョンで生きているオブジェクトは、ある年齢に達する(特定の数のコレクションを生き残った)までそのリージョンに残り、その時点で old generation に移動されます。old generation が満杯になると、young generation と old generation の両方で未使用のオブジェクトのスペースを再利用するフルコレクションがトリガーされます。通常、young generation のコレクションはフルコレクションよりもはるかに高速ですが、メモリフットプリントを低く保つためにはフルコレクションを行うことが重要です。デフォルトでは、シリアル GC は、良好なスループットを提供するように世代のサイズを見つけようとしますが、そうすることで利益が減少する場合はサイズをさらに大きくしないようにします。また、young generation のコレクションとフルコレクションに費やされる時間の比率を維持して、フットプリントを小さくしようとします。

最大 Java ヒープサイズが指定されていない場合、シリアル GC を使用するネイティブイメージは、最大 Java ヒープサイズを物理メモリサイズの 80% に設定します。たとえば、4 GB の RAM を搭載したマシンでは、最大 Java ヒープサイズは 3.2 GB に設定されます。同じイメージが 32 GB の RAM を搭載したマシンで実行される場合、最大 Java ヒープサイズは 25.6 GB に設定されます。これはあくまで最大値であることに注意してください。アプリケーションによっては、実際に使用される Java ヒープメモリの量ははるかに少なくなる可能性があります。このデフォルトの動作をオーバーライドするには、-XX:MaximumHeapSizePercent の値を指定するか、最大Java ヒープサイズを明示的に設定します。

GraalVM リリース 21.3 (およびそれを含む) までは、サバイバーリージョンなし、256 MB に制限された young generation、および young generation のコレクションと old generation のコレクションに費やされる時間のバランスを取るデフォルトのコレクションポリシーを使用する、シリアル GC の別のデフォルト構成を使用していることに注意してください。この構成は、-H:InitialCollectionPolicy=BySpaceAndTime で有効にできます

GC はガベージコレクションを実行するときに追加のメモリが必要であることに注意してください(最悪の場合、最大ヒープサイズの2倍ですが、通常は大幅に少なくなります)。したがって、常駐セットサイズである RSS は、ガベージコレクション中に一時的に増加する可能性があり、これはメモリ制約のある環境(コンテナなど)では問題になる可能性があります。

パフォーマンスチューニング #

GC のパフォーマンスとメモリフットプリントをチューニングするために、次のオプションを使用できます

  • -XX:MaximumHeapSizePercent - 最大 Java ヒープサイズがそれ以外に指定されていない場合、最大 Java ヒープサイズとして使用される物理メモリサイズのパーセンテージ。
  • -XX:MaximumYoungGenerationSizePercent - 最大 Java ヒープサイズのパーセンテージとしての young generation の最大サイズ。
  • -XX:±CollectYoungGenerationSeparately (GraalVM 21.0 以降) - フル GC が young generation を old generation とは別々に収集するかどうかを決定します。有効にすると、フル GC 中のメモリフットプリントが減少する可能性があります。ただし、フル GC には時間がかかる場合があります。
  • -XX:MaxHeapFree (GraalVM 21.3 以降) - コレクション後に割り当てのために予約されたままであり、オペレーティングシステムに返されない空きメモリチャンクの最大合計サイズ(バイト単位)。
  • -H:AlignedHeapChunkSize (イメージのビルド時にのみ指定可能) - バイト単位のヒープチャンクのサイズ。
  • -H:MaxSurvivorSpaces (GraalVM 21.1 以降、イメージのビルド時にのみ指定可能) - young generation に使用されるサバイバースペースの数。つまり、オブジェクトが old generation に昇格される最大年齢。値が 0 の場合、young collection を生き残ったオブジェクトは、old generation に直接昇格されます。
  • -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 が young generation のコレクションに費やす時間を決定します。デフォルト値の 50 では、GC は young collection と full collection に費やす時間のバランスを取ろうとします。この値を増やすと、フル GC の数が減り、パフォーマンスが向上する可能性がありますが、メモリフットプリントが悪化する可能性があります。この値を減らすと、フル GC の数が増え、メモリフットプリントが改善される可能性がありますが、パフォーマンスが低下する可能性があります。

G1 ガベージコレクタ #

Oracle GraalVM は、Java HotSpot VM の G1 GC に基づく Garbage-First (G1) ガベージコレクタも提供しています。現在、G1 ガベージコレクタは、Linux AMD64 および AArch64 アーキテクチャ上のネイティブイメージで使用できます。(GraalVM Community Edition では利用できません。)

有効にするには、native-image ビルダーにオプション --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、および evacuating GC です。レイテンシーとスループットの間で最適なバランスを提供することを目指しています。

スループットを向上させるため、一部の操作は常にストップ・ザ・ワールドのポーズで実行されます。グローバルマーキングのようなヒープ全体に対する操作など、アプリケーションが停止した状態では時間がかかる他の操作は、アプリケーションと並行して同時実行されます。G1 GC は、長い時間軸で見ると、設定されたポーズ時間目標を高確率で満たすように努めます。ただし、特定のポーズに対して絶対的な確実性はありません。

G1 は、ヒープを同じサイズのヒープ領域のセットに分割します。各領域は仮想メモリの連続した範囲です。領域は、メモリ割り当てとメモリ回収のための GC 内部の単位です。任意の時点で、これらの各領域は空であるか、特定の世代に割り当てられている場合があります。

最大 Java ヒープサイズが指定されていない場合、G1 GC を使用するネイティブイメージは、最大 Java ヒープサイズを物理メモリサイズの 25% に設定します。たとえば、4GB の RAM を搭載したマシンでは、最大 Java ヒープサイズは 1GB に設定されます。同じイメージが 32GB の RAM を搭載したマシンで実行される場合、最大 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:G1HeapRegionSize=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 ヒープ設定が自動的に決定されます。この自動メカニズムをオーバーライドして、実行時にヒープサイズを明示的に設定するには、次のコマンドラインオプションを使用できます。

  • -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

圧縮参照 #

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

  • -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

詳細情報 #

お問い合わせ