by shigemk2

当面は技術的なことしか書かない

クラスファイルの解析とJVMのトリッキーなところの復習

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;

f:id:shigemk2:20150526211004p:plain 引用: http://www.ne.jp/asahi/hishidama/home/tech/java/bytecode.html

MIPSとかPowerPCとか見ててもスタックにpushするのがstoreで、popするのがloadとなっているはずなのだが、どうも逆みたいだ。

参考。

Javaバイトコードメモ(Hishidama's Java ByteCode Memo)

@IT:Java TIPS -- javapコマンドでクラスファイルを読む