Java Virtual Machine

Revised: 2nd/Nov./2003; Since: 26th/Jan./2003

JVM (Java Virtual Machine) や GC (Garbage Collector) の動作を知っておくことで、効果的なパフォーマンス向上の戦術を練ることができます。

Java の実行方式

プログラミング言語としての Java の詳細は別項にて紹介済みですが、ここで復習してみましょう。

Java はインタープリタ型言語に分類されますが、二段階を経て実行されます。

  1. ソースコードが格納されたファイル (*.java) を、コンパイラ javac が、バイトコードにコンパイル
  2. バイトコードが格納されたクラス・ファイル (*.class) を、JVM (Java Virtual Machine) が、ネイティブコードに変換しながら実行

コンパイラ javac による事前のコンパイルと、インタープリタ JVM による実行時のコンパイルの二回が必要になります。

Windows と UNIX/Linux、Intel 互換 CPU (IA: Intel Architecture) と POWER 系 CPU とでは、実行可能な命令セットが異なるので、JVM もそれぞれの OS 用にコンパイルしたものを導入する必要があります。通常の環境では、コンパイル済みのインストールイメージが用意されておるので、それを選んで導入します。Java の実行環境である JRE や、 JRE を含む開発環境である J2SDK には、それぞれの環境用の JVM が含まれています。

プラットフォームで実行可能なソフトウェアをネイティブ・コードと呼びますが、 JVM はマシン・ネイティブなんですね。Java のコンパイル結果であるバイトコードは JVM ネイティブであって、バイトコードを受け取った JVM はマシン・ネイティブのコードに変換して実行します。一つのバイトコードを、別のマシン上で実行できるように、JVM は設計されているわけです。バイトコードのポータビリティが Java の大きな特徴であることは論を待ちません。"Write Once, Run Anywhere" は Sun Microsystems が推進している Java の標語です。

JVM と Java 実行環境

JVM は、Java アプリのクラスを言語仕様にしたがって実行する製品です。実際に Java アプリを実行するためには、言語仕様に準拠したクラス・ライブラリを JVM に登録する必要があるでしょう。特に、J2SE のコア・パッケージは必須となります。Java の実行環境は、JVM と、 JVM で実行可能な言語仕様に従って書かれたクラスの集合(クラス・ライブラリ)の二つからなります。

JVM へのクラス・ライブラリの登録は、OS の環境変数 CLASSPATH によって設定します。JVM 実行時引数として、 -classpath フラグのオペランドに指定することもできます。JRE や SDK 導入時に、コア・パッケージは認識されるようになっているので、ベンダーのパッケージや独自開発のライブラリを登録するときには、これらの作業が必要となります。

JVM の動作

JVM は、以上見てきたとおり、Java を実行するプラットフォームです。バイトコードは、 JVM にとってのアセンブリ言語であると言えます。Java アプリの開発者は、JVM という仮想的なマシン用にコーディング/コンパイルすることになります。

インタープリタとしての JVM

JVM は仮想的なマシンとして働くために、Windows や UNIX/Linux のようなネイティブの OS の働きと同様の処理を実行します。ここで必要となる処理は、バイトコードをメモリ上にロードして、命令を一つずつ取り出し、レジスタに登録、CPU で実行して結果をメモリ上に書き戻して、次の命令をまた読み込むという作業です。JVM 自体が、ネイティブの OS によってメモリ上にロードされた、OS にとってのクライアント・アプリ(プロセスの一つ)に他なりませんが、そのメモリ領域内に、バイトコードを実行するためのメモリ構造(ヒープとスタック)を持っているのです。

JVM の仕様は Sun Microsystems によって決められています。他ベンダーによる製品は、この使用にしたがって実装したものであり、 Sun Microsystems によって実装された製品は、 J2SE (Java 2, Standard Environment) に含まれています。

JVM のバージョン

コマンドラインから、次のコマンドを入力してください。

C:\>java -version

ご利用の環境に JVM が導入済みの場合、次のような応答が返ってきます。導入されている JVM のバージョンが出力されています。

java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)

Sun Microsystems の JVM は、クライアント用の Java HosSpot Client VM と、サーバ用の Java HotSpot Server VM の二種類が用意されています。上の出力例では、Java HotSpot Client VM がデフォルトとして表示されています。

JIT と HotSpot

JVM 1.3以上では、HotSpot が実装されました。JVM の JIT (Just-In-Time) コンパイラは、バイトコードを実行時にコンパイルするのではなく、実行前にコンパイルしておいたものを実行する仕組みです。Java HotSpot VMは、特に負荷の高いところ (HotSpot) を検出して事前にコンパイルしてから実行する JVM です。

ソフトウェアの実行時コストは、コードの全体で満遍なく消費されているのではなく、コードのごく一部で大半が消費されることが多いので、HotSpot の検出と事前コンパイルは、パフォーマンス向上の観点では、非常に効果が高い方法です。

javac によって生成されたバイトコードが、JVM によって実行中に変更されることはありませんが、それをどのように取り扱うかは、JVM のバージョンによって異なります。特に、HotSpot VM の場合は、実行時のリソースの使用率に応じて動的に処理を変更するので、コーディング・レベルで動作を予測して最適化を施すことは無意味だといえます。

JVM とバイトコード

バイトコードは、ソースコードを javac によってコンパイルすることで生成されます。バイトコードは JVM にとってのアセンブリ言語です。クラス・ファイルを、JVM で逆アセンブル (disassemble) することで、バイトコードを得ることができます。一般に、オブジェクト・コードからソースコードに逆変換することを、リバース・エンジニアリングと呼びます。

次のコードを、コンパイルしたクラスファイルを逆アセンブルしてみましょう。

class DisAssembleDemo {
	public static void main(String[] args) {
		System.out.println("Hello, world!");
	}
}

コンパイルは javac で行い、逆アセンブルは javap で行います。

C:\java>javac DisAssembleDemo.java

C:\java>javap -c DisAssembleDemo
Compiled from "DisAssembleDemo.java"
class DisAssembleDemo extends java.lang.Object{
DisAssembleDemo();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   ldc     #3; //String Hello, world!
   5:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   8:   return

}

JVM の動作について理解することは、インフラの導入/保守作業に従事する方はもちろん、アプリ開発者にとっても、実行プラットフォームに関する理解は益するところが大きいと考えます。

Sun の文書をリストしておきます。



Copyright © 2003 SUGAI, Manabu. All Rights Reserved.
SEO [PR] !uO z[y[WJ Cu