JVMの関数はどのようにして呼び出されるか
関数が起動するとフレームというものが作成される。このフレームは、関数が終了したとき(正常リターン、例外に関わらず)破棄される。このフレームは以下の3つを持つ。
- ローカル変数
- オペランド・スタック
- コンスタント・プール
例
関数hogeはint型の引数を3つを取り、その合計を返す。関数mainはその結果を標準出力に出良くする。
public class Switch { public static void main(String[] args) { int n = hoge(2, 4, 5); System.out.println(n); } public static int hoge(int a, int b, int c) { return a + b + c; } }
これをコンパイルすると以下のバイトコードが得られる。
public static void main(java.lang.String[]); Code: 0: iconst_2 1: iconst_4 2: iconst_5 3: invokestatic #16; //Method hoge:(III)I 6: istore_1 7: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream; 10: iload_1 11: invokevirtual #26; //Method java/io/PrintStream.println:(I)V 14: return public static int hoge(int, int, int); Code: 0: iload_0 1: iload_1 2: iadd 3: iload_2 4: iadd 5: ireturn
関数を呼び出す命令がinvokestaticだ。引数を渡すには、予めオペランドスタックと呼ばれるスタックに値を入れておき、その後でinvokestaticを呼び出す必要がある。
iconst_n命令はint型の値nをオペランドスタックに格納する命令だ。iconst_のnは-1〜5の範囲で利用出来るが、それ以上の値を格納する場合はbitpush
値の取り出しはiload_n命令を使う。この関数を呼び出した時にオペランドスタックに入っていた値は、新規フレーム作成時にローカル変数という領域に割り当てられる。このiload_n命令を使ってint型のローカル変数を読み出すことができる。これも型毎に命令が存在する。
戻り値は*return(int型の場合はireturn)を使う。これはカレントフレームのオペランドスタックに格納されている値をポップして、呼び出し元カレントフレームのオペランドスタックに格納する。上記の例の場合、4:のiadd命令はオペランドスタックから2つ値をポップし、その結果を再びオペランドスタックに格納する。なので5:でireturnを呼んだ時にその結果が呼び出し元のカレントフレームのオペランドスタックに格納される。