JavaのHello, Worldプログラムのクラスファイルの読み方が書いてある。
@IT:Java TIPS -- javapコマンドでクラスファイルを読む
constant poolまでのところは割と前置きなので、覚えたいときに覚えておく。
ポイントは、getstaticからinvokevirtualのところまでだ。
- Systemクラスのoutフィールドを読み込み(getstatic)
- Stringオブジェクトの“Hello World!”読み込み(ldc)
- その文字列を引数にしてprintlnメソッドを呼び出し(invokevirtual)
public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 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 LineNumberTable: line 3: 0 line 4: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String;
それはScalaでも変わらない。
public void main(java.lang.String[]); flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: getstatic #19 // Field scala/Predef$.MODULE$:Lscala/Predef$; 3: ldc #21 // String hello, world 5: invokevirtual #25 // Method scala/Predef$.println:(Ljava/lang/Object;)V 8: return LocalVariableTable: Start Length Slot Name Signature 0 9 0 this LHelloWorldScala$; 0 9 1 args [Ljava/lang/String; LineNumberTable: line 3: 0
違いがあるとすると、コメントアウト部分で違うところと、LocalVariableTableに載っているデータの中身と数。
LocalVariableTableとは何か?というと、これはデバッグ情報で、処理には直接関係のない情報だが、JVMにはローカル変数という概念があり、そこがJVMのトリッキーなところ。
たとえば、こんな感じのCプログラムがあったとして、
int add() { int a = 1; int b = 2; return a + b; }
これをPowerPCのGCCでコンパイルしたものを逆アセンブルすると、以下のような結果が得られる(逆アセンブル結果はかなり最適化してあるが)
$ powerpc-elf-gcc -nostdlib -g -O add.c
01800054 <add>: 1800054: 38 60 00 03 li r3,3 1800058: 4e 80 00 20 blr
普通は、レジスタに値を入れて、計算をする、という流れになるはずだが、JVMにはレジスタというものがない。スタックとローカル変数と呼ばれる領域で値のやり取りをするような形となる。
下の絵は以下のプログラムのアセンブリ的な流れを追っかけたものを引用したものであるが、JVMにはレジスタ的なものが存在せず、loadがローカル変数の値をスタックに入れる、storeがスタックの値をローカル変数に入れる、という若干トリッキーな感じになっている。
int n = 123; int m = 3; int t = m + n;
引用: http://www.ne.jp/asahi/hishidama/home/tech/java/bytecode.html
MIPSとかPowerPCとか見ててもスタックにpushするのがstoreで、popするのがloadとなっているはずなのだが、どうも逆みたいだ。