Java仮想マシンにおけるプログラム実行の仕組み
以下に示す簡単なJavaメソッドを用いて、JVMでの実行プロセスを詳細に説明します。
public int calculateSum(int x, int y) {
return x + y;
}
1. Javaコードの作成とコンパイル
JavaソースコードをCalculator.javaとして保存し、javacコンパイラでバイトコードに変換します:
javac Calculator.java
この処理によりCalculator.classファイルが生成され、Javaバイトコードが含まれます。
2. JVMへのバイトコード読み込み
以下のコマンドでプログラムを実行すると:
java Calculator
JVMが起動し、バイトコードの実行が開始されます。
3. クラスローダーの役割
JVMのクラスローダーがCalculator.classファイルを読み込み、クラス定義をメモリに展開します。これにはメソッドのバイトコードも含まれます。
4. メソッドエリア
読み込まれたクラスとメソッドのバイトコードはJVMのメソッドエリアに格納されます。calculateSumメソッドのバイトコードは以下のようになります:
public int calculateSum(int x, int y);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
5. 実行エンジンの動作
インタプリタ実行
JVMの実行エンジンがバイトコードを逐次解釈・実行します:
- プログラムカウンタ:各スレッドが独立して保持し、現在実行中のバイトコード命令を指します
- オペランドスタック:演算対象と中間結果を保持します
- ローカル変数テーブル:メソッドパラメータとローカル変数を格納します
calculateSum(4, 5)の呼び出し例では:
バイトコード命令の実行フロー
- 命令 0:
iload_1- 第一引数x(値4)をオペランドスタックに積む - オペランドスタック:[4] - 命令 1:
iload_2- 第二引数y(値5)をオペランドスタックに積む - オペランドスタック:[4, 5] - 命令 2:
iadd- オペランドスタックから2つの値4と5を取り出し、加算実行後、結果9をスタックに戻す - オペランドスタック:[9] - 命令 3:
ireturn- オペランドスタックから値9を取り出し、メソッド戻り値として返す
ローカル変数テーブルの状態
calculateSumメソッド呼び出し時のローカル変数テーブル:
- ローカル変数テーブル:[this, 4, 5]
-
this:現在のオブジェクト参照(インスタンスメソッドの場合) -4:パラメータxの値 -5:パラメータyの値
6. 実行エンジンの詳細動作
プログラムカウンタの推移
バイトコード命令実行時のプログラムカウンタの変化:
- 初期状態:PC = 0
iload_1実行後:PC = 1iload_2実行後:PC = 2iadd実行後:PC = 3ireturn実行後:メソッド終了、呼び出し元に戻る
7. JITコンパイラの最適化
calculateSumメソッドが頻繁に呼び出される場合、JVMのJITコンパイラはバイトコードをネイティブマシンコードに変換し、実行効率を向上させます。コンパイル後のマシンコードはCPUが直接実行するため、インタプリタによる逐次解釈が不要になります。
8. 呼び出し元への戻り
メソッド戻り値9が呼び出し元に渡され、後続のコード実行が継続されます。
実行プロセスの全体図
Javaコード -> コンパイラ(javac) -> バイトコード(Calculator.class) -> JVM
-> クラスローダー -> メソッドエリア -> 実行エンジン
-> [プログラムカウンタ, オペランドスタック, ローカル変数テーブル]
-> バイトコード逐次実行(インタプリタ)
-> JITコンパイル(オプション)
-> ネイティブマシンコード実行(性能向上)
主要コンポーネントのまとめ
- クラスローダー:バイトコードをメモリに読み込み
- メソッドエリア:クラスとメソッドバイトコードを格納
- 実行エンジン:バイトコードの解釈実行またはネイティブコードへのコンパイル
- プログラムカウンタ:現在実行中のバイトコード命令を指示
- オペランドスタック:演算対象と中間結果を保持
- ローカル変数テーブル:メソッドパラメータとローカル変数を管理
以上の説明により、JVM内部での命令実行フローの全体像と、CPU、実行エンジン、プログラムカウンタ、オペランドスタック、マシン命令間の関係性が明確になりました。