Revised: 2nd/Nov./2003; Since: 26th/Jan./2003
JVM のメモリ構造は、スタックとヒープに大別されます。ヒープ (Heap) は GC の対象で、JVM 起動時に割り当てられる広大な領域です。Java 仮想マシン・スタック (Java Virtual MAchine Stack) はスレッドごとに割り当てられる、メソッド起動ごとにフレーム (Frame) と呼ばれるデータを出し入れする線形のデータ構造です。クラスのインスタンスなどはヒープに格納しますが、インスタンスのような GC 対象となる動的なデータと、クラス構造などの静的なデータは、別の領域に保持し、静的な構造を保持する領域をメソッド・エリア (Method Area) と呼びます。
図:JVM のメモリ構造 |
---|
JVM はプロセスの一つとして、OS から CPU 時間をディスパッチされます。プロセス内部では、スレッドという単位でアプリケーションにリソースを割り当てます。アプリケーションは、最低でも一つのスレッドを持ち、言語使用でマルチ・スレッドをサポートしていることは、Java の特徴の一つになります。
スレッドが起動すると、JVM は、自分のプロセスのメモリ構造内に Java 仮想マシン・スタック (Java Virtual Machine Stack) を割り当てます。一般に、Java 仮想マシン・スタックは固定長の線形データ構造で、後入れ先出し (LIFO: Last-In-First-Out) の待ち行列(Stack)を実装し、データの出し入れをプッシュとポップと呼びます。
スタック上のデータの単位をフレームと呼びます。当該メソッドのローカル変数の配列や、処理対象のデータを保持するスタック (Operand Stack)、当該メソッドのクラスの実行時コンスタント・プールへの参照を保持します。
メソッドが起動されると、スタック上にフレームと呼ばれる単位のデータが積み上げられ(プッシュされ)、呼び出されたメソッドがアクティブになります。当該メソッドの処理が終了すると、当該フレームは削除され(ポップされ)、呼び出し元のメソッドが再びアクティブになります。実行中のメソッドとそのフレームを、カレント・メソッド、カレント・フレームと呼びます。データ構造上では、Java 仮想マシン・スタックの先頭のフレームがカレント・フレームになって、対応するメソッドがアクティブになります。
フレームはメソッド終了時にポップされて破棄され、スタック自身もスレッド終了と共にメモリ上からドロップされる、寿命の短いデータ・エリアです。
固定長の線形データ構造であるスタックに対する、フレームのプッシュ/ポップによる管理は、簡潔で高速です。ローカル変数のプリミティブ型の値や、オブジェクトへの参照などのデータが含まれているので、Java仮想マシン・スタック上のフレームをトレースすることで、アプリケーションの動作の概略を解析することが可能です。
ヒープ (Heap) は、JVM 毎に一つ割り当てられ、全てのスレッドから共有されます。一般に、ヒープは広大な領域を確保し、実行されるアプリケーションの必要に応じて拡大します。
オブジェクトのインスタンス、メソッド・エリアなどの、アプリケーション実行時に必要になるデータを保持します。ヒープの一部であるメソッド・エリア (method area) は、JVM 起動時に割り当てられ、実行時コンスタント・プール、フィールドやメソッドのデータなどのクラスの構造が格納されます。実行時コンスタント・プール (Runtime Constant Pool) は、クラス/インタフェース毎に生成され、リテラルやメソッド/フィールドへの参照が格納されます。
ヒープは、全てのスレッドのスタックから共有され、JVM が起動している限り持続する寿命の長いデータ・エリアです。
スタックのように固定サイズの直線的なデータ構造ではないために、複雑なデータ管理が必要となり、ヒープ領域にあるデータに対するアクセスは低速になります。アプリケーションで動的に割り当てを行うために、領域の断片化(フラグメンテーション)が発生しやすく、メモリ・リーク/領域違反が発生するのもこの領域です。Java では、ヒープ上のデータ・エリアの管理は、GC (Garbage Collector)が受け持っており、アプリケーションが明示的に解放することはできません。
これらのデータ領域間の参照の繰り返しによってアプリケーションは動作し、多くのJVM実装では、必要があればデータ・エリアは拡張していきます。実行中にメモリ領域を確保できない場合は、対応する例外が投げられます。代表的な例外は、次の二つです。
java.lang.OutOfMemoryError
...スタック、ヒープなどのデータ・エリアを割り当てられないときに発生java.lang.StackOverflowError
...スタックでオーバーフローが発生したときに発生ここで挙げたデータ・エリアのサイズは、起動時にオプションで指定可能になっています。指定できるオプションは、実装によって異なりますが、SunのJVMでは次のオプションが指定可能です。
-Xms[n]
...ヒープの初期サイズ。[n]は1024-1MB。単位は、バイトを表す無指定か、k,K,m,Mのいずれか。デフォルトは2MB。-Xmx[n]
...ヒープの最大サイズ。[n]の指定は-Xmsと同様。デフォルトは64MB。-Xss[n]
...スタックのサイズ。デフォルトは512KB。他のオプションのサマリーを、コマンド"java -X"でリストすることができます。詳細は、"Java HotSpot VM Options" を参照してください。また、ベンダー、バージョンごとに、指定可能なオプションの種類とデフォルト値が異なっているので、各社のドキュメントを参照する必要があります。
オブジェクト指向とプロシージャ志向の区別をハイライトしてみます。
プロシージャ指向のサブルーチンの場合は、サブルーチンがヒープに展開され、その参照がスタックに積み上げられます。サブルーチンが呼び出されると、サブルーチンコールはスタックに積み上げられて、コンピュータ制御がヒープ上のサブルーチンのコードに移ります。コンピュータ制御がサブルーチンから抜けると、先ほど積み上げられたサブルーチンコールはスタックから取り除かれ、呼び出し元のコードに制御が戻ります。
オブジェクトのメソッドの場合も、サブルーチンと同様に処理されます。その最大の違いは、データの取り扱いです。サブルーチンの場合、一時的にしか存在しないサブルーチンコールに引数として渡すため、メモリ上に持続的に展開されたコードにはデータは保持されていません。一方、オブジェクトの場合、ヒープに持続的に展開されているコード内にデータが埋め込まれており、メソッドは自身が属するオブジェクトのデータだけを処理できます。
サブルーチンもオブジェクトもメモリ上に持続的に展開されています。サブルーチンはデータを持たない処理であり、オブジェクトはデータと処理が一つになったものです。この違いの意味するところが、今は漠然としたものに感じるかもしれませんが、アプリケーション開発の分析と設計を決定的に異なるものとします。
Apache Tomcat や IBM Webspehre Application Server (WAS) のような、サーブレットや JSP のようなサーバサイドの Java 実行環境を、アプリケーション・サーバと呼びます。アプリケーション・サーバは、多くの場合、 JVM 上で実行される Pure Java アプリケーションとして実装されています。
詳細は、製品ごとに異なりますが、管理サーバがアプリケーションサーバをサブ・プロセスとして起動します。管理サーバも、アプリサーバも、JVM として起動し、各々の機能は JVM 上のアプリとして実装されます。
例えば、アプリサーバは、HTTP サーバの機能を実装しており、Embedded HTTP Server と呼ばれていますが、これらは JVM 上のスレッドとして起動する Pure Java Application として実装されているものです。
SEO | [PR] !uO z[y[WJ Cu | ||