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 命令を使う。なお、floatの場合はfconst命令など、型によって別の命令を使う。

値の取り出しはiload_n命令を使う。この関数を呼び出した時にオペランドスタックに入っていた値は、新規フレーム作成時にローカル変数という領域に割り当てられる。このiload_n命令を使ってint型のローカル変数を読み出すことができる。これも型毎に命令が存在する。

戻り値は*return(int型の場合はireturn)を使う。これはカレントフレームのオペランドスタックに格納されている値をポップして、呼び出し元カレントフレームのオペランドスタックに格納する。上記の例の場合、4:のiadd命令はオペランドスタックから2つ値をポップし、その結果を再びオペランドスタックに格納する。なので5:でireturnを呼んだ時にその結果が呼び出し元のカレントフレームのオペランドスタックに格納される。