前置き
Javaファイルをコンパイルしたときに生成されるclassファイル。
これがJVMでどのように実行されているかに興味を持ったので調べてみた備忘録。
動作環境
Shogo-no-MacBook-Pro$ java -version java version "1.8.0_92" Java(TM) SE Runtime Environment (build 1.8.0_92-b14) Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
ステップ1
public class Calculator{ public static int return1(){ return 1; } }
まずは単にint型の1を返却する静的メソッドを持つCalculatorクラスを作成した。 早速、ソースコードをコンパイルする。
javac Calculator.java
コンパイルすると、classファイルが生成される。 javapコマンドを実行してみよう。
javap -v Calculator
すると、以下が標準出力に表示されるはずだ。
Classfile /Users/Shogo/Java_src/Test/Calculator.class Last modified 2018/07/17; size 250 bytes MD5 checksum 16f085b520f032ca19fbaf234c3ce4f4 Compiled from "Calculator.java" public class Calculator minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#12 // java/lang/Object."<init>":()V #2 = Class #13 // Calculator #3 = Class #14 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 return1 #9 = Utf8 ()I #10 = Utf8 SourceFile #11 = Utf8 Calculator.java #12 = NameAndType #4:#5 // "<init>":()V #13 = Utf8 Calculator #14 = Utf8 java/lang/Object { public Calculator(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 public static int return1(); descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_1 1: ireturn LineNumberTable: line 3: 0 } SourceFile: "Calculator.java"
javapコマンドは、classファイルを可視化するコマンドである。 数多くの情報が出力されているが、今回はreturn1メソッドの部分だけに着目して読み解いてみよう。
public static int return1(); descriptor: ()I flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_1 1: ireturn LineNumberTable: line 3: 0 } SourceFile: "Calculator.java"
Javaの実行環境であるJVM(Java Virtual Machine)は、スタックマシン上でコードが実行していく。classファイルでもスタックマシンで動作するコードが確認することができる。
iconst_1 定数1をスタックに詰める命令である。 スタックマシンでは、returnする値をスタックに詰める必要がある。
ireturn スタックのトップを呼び出し元に返却するための命令である。 ここでは、先ほど詰めた定数1をそのまま返却する。 ちなみに、先頭に「i」がついているのはint型のことを示している。
ステップ2
次は、引数を取る返却するメソッドretrunAを作成してみる。
public class Calculator{ public static int returnA(int a){ return a; } }
そして、javapした結果が以下である。
public static int returnA(int); descriptor: (I)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: iload_0 1: ireturn LineNumberTable: line 7: 0 } SourceFile: "Calculator.java"
0.iload_0 ステップ1の流れから、returnする値をスタックに詰める必要がある。 つまり、ここでは引数をロードして、スタックに詰める命令に該当する。
- ireturn ステップ1と同様なので割愛。
ステップ3
それでは次は、引数に1を加算して返却するメソッドを作ってみよう。
public class Calculator{ public static int plus1(int a){ return a + 1; } }
public static int plus1(int); descriptor: (I)I flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: iload_0 1: iconst_1 2: iadd 3: ireturn LineNumberTable: line 11: 0
さて、これまで見てきた二つのメソッドを合体させたような命令となっている。 plus1メソッドでは以下が実行される。
iload_0 引数aをスタックにpushする
iconst_1 定数1をスタックにpushする
iadd 新しく登場した命令だが、int型をaddする命令であることが容易に想像できる。 スタックに積まれている二つの値(aと1)を加算し、スタックにpushする命令に当たる。
ireturn 値を返却する。
ステップ4
さて、ステップ3のインスタンスメソッドを静的メソッドに変更してみるとどうなるだろうか?
public class Calculator{ // staticを取ってみる public int plus1(int a){ return a + 1; } }
public int plus1(int); descriptor: (I)I flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: iload_1 1: iconst_1 2: iadd 3: ireturn LineNumberTable: line 11: 0
iload1 ステップ3のケースと比較すると、iload_0がiload_1になっていることがわかる。 実はインスタンスメソッドの場合、iload_0ではthisを指すことになっているためである。(ステップ5で実際に見てみよう) つまり一番目の引数はiload_1に該当する。
iconst_1
- iadd
- ireturn
これまでと同様なので割愛。
ステップ5
thisのインスタンスを指す場合はiload_0を指すとのことなので、早速確認してみよう。
public class Calculator{ private int number = 1; public int plus1(){ return this.number + 1; } }
フィールドとしてnumber変数を導入し、 plus1メソッドではそのフィールドに1を加算するように修正した。
public int plus1(); descriptor: ()I flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: getfield #2 // Field number:I 4: iconst_1 5: iadd 6: ireturn LineNumberTable: line 5: 0 } SourceFile: "Calculator.java"
aload_0 thisインスタンスをスタックに詰める命令である。
getfield #2 スタックに詰まっているthisインスタンスの属性値を取得し、スタックに詰める命令である。 フィールドであるnumberの値を取得し、スタックに詰めている。
iconst_1
- iadd
- ireturn
これまでと同様のため、割愛する。
関連リンク
The Java® Virtual Machine Specification