🤖

Java仮想マシン(JVM)を読解しながら理解する #06

に公開

https://zenn.dev/h_kohe/articles/5cbf90982e0c29
こちらの記事の続きです

よんでいる公式ドキュメント
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-2.html

今週末は今更ながらにClaude Codeをプロプランに切り替えたのですが、
想定より楽しくなってしまいずっと触っていたらあっという間に深夜になってしまいました・・・😇
https://github.com/koheihida/digital-narrative-cascade

GitHub Pagesにも公開しているのでよかったら観てみてください!!
もう少し手を加えたい点はいくつかあるのですが、頭の中にあったことはだいたい表現できてきています!!(最初の読み込みに時間がかかる問題がありますが。。。)
https://koheihida.github.io/digital-narrative-cascade/

余談はさておき本題にはいります・・・・!!

2.5.3. Heap(ヒープ)

Javaを学習したらどのテキストにも必ず登場する単語なので恐らく初見の人はすくないはず・・・
けど、データ領域である 以外の説明ができない人も多いはず・・・

ヒープとは??

•ヒープ(Heap) は、JVM が用意する実行時のデータ領域のひとつ
•すべての オブジェクトインスタンスや配列 が、このヒープ領域からメモリを確保されます
•全スレッドで共有される領域

一応データ領域について整理しておきます!!

領域 共有/スレッド単位 主な役割 例外
Heap(ヒープ) 共有 オブジェクト・配列の格納。GCによる自動管理対象 OutOfMemoryError: Java heap space
Method Area(メソッド領域) 共有 クラス情報、メソッド、フィールド、定数プールなどを格納。Java 8以降はMetaspace OutOfMemoryError: Metaspace
PC Register(PCレジスタ) スレッドごと 現在実行中の命令位置を保持。Javaメソッド実行中はバイトコードのオフセットを記録、native 実行中は未定義 なし
JVM Stack(Java仮想マシンスタック) スレッドごと メソッド呼び出しごとにフレームを積む。フレームにはローカル変数、計算途中の値、戻り先などが含まれる StackOverflowError, OutOfMemoryError
Native Method Stack(ネイティブメソッドスタック) スレッドごと native メソッドの実行に利用。C/C++などJVM外の処理を扱う OutOfMemoryError

ヒープのライフサイクル

•JVM起動時(サーバーがプロセスとして立ち上がる時)に作成される
•JVM終了時(サーバープロセスを停止した時)に破棄される
•開発者が明示的に解放することはできない

GC(ガーベジコレクション)

不要になったオブジェクトを自動で解放し、メモリを再利用可能にすること

「不要」とはどう判定されるのか?

JVMでは 「参照が到達可能かどうか」 で判定

状態 説明 GC対象
到達可能 (reachable) ルート(root)から参照されているオブジェクト - スタック上の変数
- クラスの静的フィールド
- ネイティブコードの参照
❌ GC対象外(まだ利用中)
到達不能 (unreachable) どの参照からもたどれないオブジェクト - ローカル変数のスコープを抜けた後のオブジェクト
- 参照が null に置き換えられたオブジェクト
✅ GC対象(不要とみなされる)

なるほど・・・🤔
メモリを使いきってしまった場合はどうなるのだろうか??
達成可能な状態だどメモリが解放されないとなると
メモリを使い切ってしまった場合は・・・??
※後述の例外を参照

ヒープサイズ

ヒープサイズの初期値/最大値は「実行環境(物理メモリ量やOS)」によって変わるため、
デフォルトサイズや拡張方法は実装依存になる

実装(HotSpot, OpenJ9, GraalVMなど)が、
実行環境に応じて「初期ヒープサイズ」「最大ヒープサイズ」を計算をする

以下のコマンドを実行すれば自分の環境でJVMが使用しようとしているヒープサイズが確認できます!!

java -XX:+PrintFlagsFinal -version | grep HeapSize

私の環境で実行すると以下のような出力がありました。

   size_t ErgoHeapSizeLimit        = 0              {product} {default}    // ヒープ上限の「エルゴノミクス設定」。0は「制限なし」を意味する
   size_t HeapSizePerGCThread      = 43620760       {product} {default}    // GCスレッド1本あたりの目安サイズ(約41.6MB)
   size_t InitialHeapSize          = 134217728      {product} {ergonomic} // 初期ヒープサイズ(Xms)。128MBで起動
   size_t LargePageHeapSizeThreshold = 134217728    {product} {default}    // ラージページを使うかどうかの閾値(128MB)
   size_t MaxHeapSize              = 2147483648     {product} {ergonomic} // 最大ヒープサイズ(Xmx)。2GBが上限
   size_t MinHeapSize              = 8388608        {product} {ergonomic} // 最小ヒープサイズ。8MB未満には縮小しない
    uintx NonNMethodCodeHeapSize   = 5839372        {pd product} {ergonomic} // コードキャッシュ領域(非メソッドコード用 約5.6MB)
    uintx NonProfiledCodeHeapSize  = 122909434      {pd product} {ergonomic} // コードキャッシュ領域(プロファイルなしメソッド用 約117MB)
    uintx ProfiledCodeHeapSize     = 122909434      {pd product} {ergonomic} // コードキャッシュ領域(プロファイルありメソッド用 約117MB)
   size_t SoftMaxHeapSize          = 2147483648     {manageable} {ergonomic} // ソフト制限としてのヒープ上限。MaxHeapSizeと同じ2GB

例外

計算に必要なヒープ領域が確保できない場合、OutOfMemoryError がスローされる

OutOfMemoryError
例: OutOfMemoryError: Java heap space

まとめ

•ヒープは オブジェクトと配列を置く「共有の倉庫」
•JVM起動時に作成され、終了時に破棄される
•メモリ解放は GCが自動で管理 開発者は明示的に解放できない
•サイズは固定/動的いずれも可能で、JVM実装者が指定できる
•メモリ不足になると OutOfMemoryError が発生する

Discussion