JVM を読む | JVM の構造その2 - データ領域について
前回の続きです。前回はこちらから。
このシリーズは,JVM の仕様書を読み解くためのガイドとして構成しています。
JVM の仕様書は非常に長大で難解な内容が多いため,各セクションごとに要点をまとめていきます。
また,JVM の内部構造や動作原理を知ることで,Java のパフォーマンスやセキュリティ,メモリ管理の仕組みを深く理解する試みです。
シリーズはこちらから。
第二章 The Structure of the Java Virtual Machine
JVM の仕様書の第2章は「Java Virtual Machine の構造」です。
といいましてもこの章は全7章ある JVM の仕様書の中でも特に長く,また特に複雑な内容ですので,全8回に分けて解説していきます。
ここでは Chapter 2.5 の内容(実行時時のデータ領域)を扱います。
› 2.5 Runtime Data Areas)
2.5 実行時のデータ領域(JVM は実行時に様々なデータ領域にアクセスします。
データ領域は,格納するものによって次のように分類されます:
-
JVM 全体に存在するもの:JVM の起動時に割り当てられ,プログラムの実行中に共有されます。
- ヒープ
- メソッド領域
- 実行時定数プール
-
スレッドごとに存在するもの:スレッドの作成時に割り当てられ,スレッドの終了時に解放されます。
-
pc
レジスタ - JVM スタック
- ネイティブ・メソッド・スタック
-
-
クラスごとに存在するもの:クラスがロードされるときに割り当てられ,クラスのアンロード時に解放されます。
- 実行時定数プール
- クラスのメタデータ
これらのデータ領域は JVM の実行時に必要な情報を格納し,プログラムの実行をサポートします。
ヒープ(JVM 全体)
JVM は,すべてのスレッドで共有される「ヒープ」(Heap)領域を持ちます。
全てのクラス・インスタンスと配列の実体がこの領域に格納されます。
ヒープは JVM の起動時に作成され,プログラムの実行中に動的に拡張されることがあります。
ヒープ内に格納される要素の割り当てと解放は,ガーベジ・コレクション・システムによって自動的に行われます。 ガベージ・コレクション(GC)とは,不要なオブジェクトを自動的に検出して解放する仕組みのことです。 ガベージ・コレクタ(後述)という特別なコンポーネントがこの役割を担います。
ぺやんぐ注
ヒープや JVM に関するよくある説明において,例えば Eden 領域や Survivor 領域,Old 領域などに関する詳細な説明を目にします。
これらは,あくまでも1つの JVM 実装である HotSpot JVM に特有の概念であり,他の VM 実装では異なる場合があります。
ヒープをこれらの領域に区切って管理するということは,実は JVM 仕様書では一切定義されていません。
ヒープはただ単に「オブジェクトの実体が格納される領域」とだけ説明されており,その実装は JVM の実装者に任されています。
そのため,ヒープの実装は JVM の実装によって異なることが十分にあり得ます。
例えば G1GC(Garbage-First Garbage Collector)では,ヒープは複数の小さな領域(リージョン)に分割され,それぞれのリージョンが異なる世代(Young,Old,Humongous など)に属します。これらのリージョンは動的に割り当てられ,ガベージ・コレクションの際に異なる方法で管理されます。
さらに ZGC(The Z Garbage Collector) や Shenandoah GC などの新しいガベージ・コレクタでは,ヒープの管理方法がさらに異なります。
ZGC では,ヒープを Small
,Medium
,Large
の3つのサイズ・クラスに分割し,それぞれのクラスに対して異なるガベージ・コレクション戦略を採用しています。
さらに Shenandoah GC では,ヒープを複数のリージョンに分割し,それぞれのリージョンが異なる世代に属します。
このように,ヒープの実装は JVM の実装によって大きく異なり,これが大変面白いのです。
(脚注おわり。)
メソッド領域(JVM 全体)
JVM は,すべてのスレッドで共有される「メソッド領域」(Method Area)を持ちます。
実行時定数プールやフィールド,メソッドのデータ,およびクラスのメタデータ及び構造データなどの情報がこの領域に格納されます。
なお,これは(一応)ヒープ領域内に存在していますが,或る単純な実装ではガベージ・コレクションの対象外とすることもできます。
(なぜなら,クラスの構造やメタデータ,メソッドの定義は通常,プログラムの実行中に変更されない上に,明示的にアンロードされることもないからです。)
pc
レジスタ(スレッドごと)
JVM は,各スレッドごとに pc
(Program Counter)レジスタを持ちます。
これは,現在実行している命令のアドレスをスレッドが保持・管理するためのレジスタです。
各スレッドは,どの瞬間においても(或るメソッド内の)1つの命令を実行しています。
そのメソッドが native
メソッドでない限りは,その命令のアドレスは常に pc
レジスタによって指し示されます。
(native
メソッドは JVM の外部で実行されるため,pc
レジスタは使用されません。その場合には pc
レジスタの値は不定です。)
JVM スタック(スレッドごと)
JVM は,「JVM スタック」(Java Virtual Machine Stack)と呼ばれる領域を各スレッドごとに持ちます。
これはフレームを格納するためのスタック型のデータ構造です。
フレームとは,各メソッドの呼び出しごとに作成されるデータ構造で,そのメソッドの実行に必要な情報(ローカル変数,オペランドスタック,メソッドの戻り先など)を格納します。
詳しくはJVM を読む | JVM の構造その3 - フレームについてをご覧ください。
或るメソッドが呼び出されると,そのメソッドのためのフレームが作成され,JVM スタックにプッシュ(追加)されます。
メソッドの実行が(正常に終了するか,例外がスローされるかに関わらず)完了すると,そのメソッドのフレームはポップ(削除)されます。
実行時定数プール(クラスごと)
各クラスは,「実行時定数プール」(Runtime Constant Pool)と呼ばれる領域を持ちます。
これは,クラス・ファイルの constant_pool
テーブルの値をクラス(またはインタフェース)ごとに展開して,実行時にJVM が参照できるようにしたものです。
constant_pool
テーブルには,コンパイル時に析出したリテラル(文字列や数値など)やシンボル(クラス名,フィールド名,メソッド名など),および参照(クラス,フィールド,メソッドへの参照など)が格納されます。
UTF-8
は,文字列リテラルを格納するためのエントリです。これを NameAndType
エントリや MethodRef
エントリ,FieldRef
エントリなどが参照します。
各実行時定数プールは,JVM のメソッド領域内に,クラスやインタフェースがロードされるときに作成されます。
ネイティブ・メソッド・スタック(スレッドごと)
JVM は,ネイティブ・メソッドを実行するためのネイティブ・メソッド・スタック(Native Method Stack)を持ちます。
ネイティブ・メソッドとは,Java 言語以外の言語(C や C++ など)で実装されたメソッドのことです。 JVM はこれを呼び出すために,従来の C 言語のスタックを使用します。
或る JVM の実装において,ネイティブ・メソッド・スタックを使用しない場合(そもそも呼び出せない,または自分自身が従来のスタックに依存しない場合)にはこれを実装する必要はありません。
まとめ
いかがでしたか?
JVM の実行時のデータ領域は,プログラムの実行に必要な情報を格納するための重要なコンポーネントです。
これらのデータ領域は,JVM の動作を理解する上で非常に重要です。
次回は Chapter 2.6 の内容(フレーム)を扱います。
では,よいバイト・コードライフを!
次回リンク
参考文献&リンク集
- Lindholm, T., Yellin, F., Bracha, G., & Smith, W. M. D. (2025). The Java® Virtual Machine Specification: Java SE 24 Edition.
- Lindholm, T., & Yellin, F. (1999). The Java™ Virtual Machine Specification (2nd ed.). Addison-Wesley. ISBN 978-0-201-43294-7
- Otavio, S. (2024). Mastering the Java Virtual Machine. Packet Publishing. ISBN 978-1-835-46796-1
Discussion