2025/04/23 11:24 How ZGC allocates memory for the Java heap

ロボ子、今日はZGCのメモリ割り当てについて解説するのじゃ!

ZGC、ですか。Javaのガベージコレクションですね。よろしくお願いします、博士。

ZGCは、Javaヒープのメモリを「ページ」という論理領域に分けて管理しているのじゃ。このページには、Small、Medium、Largeの3つのサイズがあるらしいぞ。

なるほど。Smallが2MB、Mediumが通常32MB、Largeが4MB超で動的サイズ、とのことですね。

そうそう。そして、JavaヒープのメモリはPage Allocatorによって管理されていて、ZGCの最小メモリ単位は2MBの「Granule」というらしい。

ヒープサイズは`-Xms`と`-Xmx`で設定できるんですよね。最小と最大を同じ値にすると固定される、と。

その通り!Page Allocatorは複数の「パーティション」を管理していて、NUMAアーキテクチャのシステムだと、ヒープが複数のパーティションに分割されるのじゃ。

NUMAが有効だと、各パーティションが特定のNUMAノードに対応するんですね。でも、論理的な関連付けで、常に正確とは限らない、と。

ZGCは物理メモリと仮想メモリを分離して管理しているのがミソじゃ。物理メモリはハードウェアRAMで、仮想メモリはフラグメンテーションに対処するためにオーバーコミットされるのじゃ。

デフォルトでは、ZGCは最大ヒープサイズの16倍、NUMAシステムでは32倍の仮想メモリを予約するんですね。

そして、Mapped Cache!これはコミット済みかつマップ済みだけど、どのページにも使われていないメモリを格納する場所じゃ。

Mapped Cacheは、自己平衡型二分探索木(赤黒木)を使って、マップされたメモリ範囲を格納するんですね。動的メモリ割り当てを避けるために。

メモリ割り当ての最初のステップは、capacityの要求じゃ。キャッシュから連続したメモリを取得しようとして、なければcapacityを増やす(新しいメモリをコミットする)のじゃ。

capacityを増やせない場合は、キャッシュからより小さな範囲を削除して、要求されたサイズにする「ハーベスティング」を行うんですね。

そう!現在のcapacityが現在の最大capacityに近い場合は、capacityの増加に加えてハーベスティングを実行するのじゃ。

割り当てが最初に失敗すると「割り当てストール」が発生し、マイナーGCがトリガーされる。2回目の割り当てが失敗すると、OutOfMemoryError(OOME)がスローされるんですね。

ハーベスティングは、マップ済みかつコミット済みだけど、仮想メモリ内で連続していない複数のメモリ範囲から物理メモリを要求することじゃ。

ハーベスティングされた範囲は、単一の連続した範囲にリマップする必要があるんですね。連続した範囲を要求できない場合、ハーベスティングされた物理メモリは仮想メモリにマップバックされ、キャッシュに挿入される、と。

どのパーティションにも割り当てを満たすのに十分なcapacityがない場合、マルチパーティション割り当てが試行されるのじゃ。これはNUMAが有効な場合にのみ有効じゃ。

デフォルトでは、ZGCはページで使用されていないメモリを定期的にアンコミットするんですね。メモリフットプリントが懸念される場合に役立つ、と。

アンコミットを無効にするには、`-XX:-ZUncommit`フラグを使うか、最小ヒープサイズと最大ヒープサイズを同じ値に設定するのじゃ。

レイテンシが重要な場合は、アンコミットを無効にする必要があるんですね。起動時間とプログラム実行中のレイテンシの間にはトレードオフがある、と。

Mediumページのサイズは、ヒープサイズの3.125%に最も近い2のべき乗に切り下げられるらしいぞ。へー。

博士、今日はZGCのメモリ管理について、とても勉強になりました!

どういたしましてなのじゃ!最後に一つ、ZGCのメモリ管理はまるで、私の部屋の片付けみたいじゃな。いつも仮想メモリ(押し入れ)はオーバーコミットなのじゃ!
⚠️この記事は生成AIによるコンテンツを含み、ハルシネーションの可能性があります。