JVM メモリ構造

Java のメモリ構造

Java で使用されるメモリモデルの構造を理解しておくと、Java プログラムを作成するうえで大いに役立つ。

JVM(Java Virtual Machine)

Java プロセスは JVM という仮想システム上で実行される。この構造により、コンパイルされた Java クラスファイル(bytecode)をさまざまな OS 環境で実行できる。

JVM

Java メモリ領域の構造

すべての Java プログラムは Java 仮想マシン(JVM)を通じて実行される。Java プログラムが実行されると、JVM はそのプログラムを実行するために必要なメモリをオペレーティングシステムから割り当てられる。

JVM は割り当てられたメモリを効率よく使うため、用途に応じて次のように複数の領域に分けて管理する。

JVM Runtime Data Area

Runtime Data Area はオペレーティングシステムから割り当てられたメモリ領域であり、Java アプリケーションを実行するときに使用されるデータを格納する領域である。
大きく Method Area、Heap Area、Stack Area、PC Register、Native Method Stack に分けられる。

メソッド(method)領域 / Static 領域

メソッド領域は、Java プログラムで使用されるクラスに関する情報と、クラス変数(static variable)が保存される領域である。
JVM は Java プログラムで特定のクラスが使用されると、そのクラス、つまり Java バイトコードであるクラスファイル(*.class)を読み込み、ロードされたクラスとインターフェースに対するランタイム定数プール(runtime constant pool)、メンバ変数(フィールド)、クラス変数(static 変数)、コンストラクタ、メソッドを保存する。

この領域に保存された内容は、プログラム開始前にロードされ、プログラム終了時に消滅する。
ランタイム定数プールには、コンパイル時に分かっている数値リテラルから、実行時に解決される必要があるメソッドやフィールドの参照まで、さまざまな定数が含まれる。

リテラル(literal)は、コンピュータサイエンス分野で固定された値を意味する。定数は変わらない変数を指し、リテラルは変わらない値(データ)を指す。

ヒープ(heap)領域

ヒープ領域は、Java プログラムで使用されるすべてのクラスオブジェクト(インスタンス)変数が保存される領域であり、JVM が管理するプログラム上でデータを保存するために実行時に動的に割り当てて使用する領域である。

プロセス実行中に動的に変化するデータは、基本的にここに割り当てられる。つまり、Java プログラムで new 演算子を使ってインスタンスが生成されると、JVM はそのインスタンスの情報をヒープ領域に保存する。
ヒープ領域は、メモリの低いアドレスから高いアドレス方向へ割り当てられる。

そして、スタック領域の変数がヒープ領域のインスタンスを参照していたものの、以後参照しなくなった場合、そのインスタンスは Java 仮想マシンのガベージコレクションによって削除される。

ガベージコレクションは、インスタンスが参照されなくなるたびに実行されるわけではない。 そうすると、ガベージコレクションの呼び出しだけで多くのリソースを使うことになり、プログラムの性能が低下する。 そのため、ガベージコレクションアルゴリズムに従って実行される。

ヒープ領域の使用期間およびスレッド共有範囲は次のとおりである。

  • オブジェクトがこれ以上使用されない、または明示的に null 宣言された場合
  • GC(Garbage Collection)の対象
  • 構成方法や GC 方法は JVM ベンダーごとに異なる場合がある。
  • すべてのスレッドで共有する。

長時間同じプロセスとして実行されるアプリケーションでは、このヒープ領域のメモリ使用を最適化することが重要である。割り当てるサイズを指定するオプションは次のとおりである。

-Xms20m -Xmx100m

上記のように指定すると、次の設定になる。

  • ヒープ領域に割り当てるメモリの初期サイズ: 20MB
  • ヒープ領域に割り当てるメモリの最大サイズ: 100MB

スタック(stack)領域

スタック領域は、Java プログラムでメソッドが呼び出されるときに、そのメソッドのスタックフレームが保存される領域である。

JVM は Java プログラムでメソッドが呼び出されると、メソッド呼び出しに関係するローカル変数とパラメータをスタック領域に保存する。 このようにスタック領域はメソッド呼び出しとともに割り当てられ、メソッド呼び出しが完了すると消滅する。スタック領域に保存されるメソッド呼び出し情報をスタックフレーム(stack frame)という。

スタック領域は Push でデータを入力し、Pop でデータを出力する。このスタックは LIFO(Last-In First-Out)方式で動作するため、最後に保存されたデータが最初に取り出される。

スタック領域は、メモリの高いアドレスから低いアドレス方向へ割り当てられる。

PC Register

  • 現在実行中の JVM 命令アドレスを持つ。CPU の PC と同じ役割だと言える。
  • プログラムの実行は CPU がインストラクション(Instruction)を実行することで進む。
  • CPU はインストラクションの実行中に必要な情報を、CPU 内部の記憶装置であるレジスタに保存する。
  • 演算結果をメモリに渡す前に保存する CPU 内部の記憶装置である。 (通常、CPU が命令を処理する過程で必要な情報は Register という CPU 内部の記憶装置に保存される。これは CPU に依存せざるを得ない。そのため、Java の思想を実現するには、この CPU 内 Register の役割を JVM 上の論理的なメモリ領域として実装する必要がある。)

Native Method Stack Area

Java 以外の言語で書かれたネイティブコードのための Stack である。
つまり、JNI(Java Native Interface)を通じて呼び出される C、C++ などのコードを実行するためのスタックである。
ネイティブメソッドのパラメータ、ローカル変数などをバイトコードとして保存する。

参考