そろそろバイトコードを読むお年頃になったので年初めにBCELを使って少し読んでみました。
BECLってのはジャカルタのバイトコード操作ライブラリ。こんなものも揃っているなんて便利な世の中です。
DOMAIN ERROR
まずここから一式をDLしてサンプルを真似しながら解析するクラスを作る。あとは「java listclass -code TestBcel」てなかんじでさくっと実行。実行できたら気合いで読む!
まずは初歩
Stringを返すだけのGetterの場合
ソース
public String getA () { return "Gara"; }
public String getA() Code(max_stack = 1, max_locals = 1, code_length = 3) 0: ldc "Gara" (16) 「Gara」をコンスタントプールに積む 2: areturn オブジェクトを返値としてreturnする
ふむふむ。文字の生成して返却すると。そのまんまだね
変数を使ってみる。
public String getA () { String aa = "Gara"; return aa; }
public String getA() Code(max_stack = 1, max_locals = 2, code_length = 5) 0: ldc "Gara" (16) 「Gara」をコンスタントプールに積む 2: astore_1 オブジェクトを1番目のローカル変数に格納。 3: aload_1 1番目のローカル変数からオブジェクトを取り出してスタックに積む 4: areturn オブジェクトを返値としてreturnする
なるほど確かにさっきのに比べてローカル変数がちゃんと使われている。
Objectを返す場合
public Date getB () { return new Date(); }
public java.util.Date getB() Code(max_stack = 2, max_locals = 1, code_length = 8) 0: new <java.util.Date> (22) オブジェクトを生成して 3: dup トップスタックアイテムの値をコピーして更にスタックの上に積む((次にinvokeすると消えちゃうからコピーが必要)) 4: invokespecial java.util.Date.<init> ()V (24) インスタンス初期化メソッドの呼び出し 7: areturn オブジェクト返却
ちょっとがんばる
読めるようになってきたんで調子乗ってみた
public void doAction() throws Exception { String a = "Pentasa"; 1 String b = "Gara"; 2 if (b.equals(a)) { 3 System.out.println(b); 4 } else { 5 a = null; 6 } if (a == null) { 7 throw new Exception(); 8 } }
public void doAction() throws java.lang.Exception Code(max_stack = 2, max_locals = 3, code_length = 39) 0: ldc "Pentasa" (31) 1さっきと同じでコンスタントプール 2: astore_1 1ローカル変数に格納 3: ldc "Gara" (16) 2 5: astore_2 2 6: aload_2 3メソッド呼び出す前にスタックから取り出し 7: aload_1 3 8: invokevirtual java.lang.String.equals (Ljava/lang/Object;)Z (33) 3メソッド呼び出し 11: ifeq #24 3スタックの値が0なら#24に飛ぶ((おおおおお!IFってこんなになってたのか!)) 14: getstatic java.lang.System.out Ljava/io/PrintStream; (39) 4Staticなクラスを取得する 17: aload_2 4変数呼び出し 18: invokevirtual java.io.PrintStream.println (Ljava/lang/String;)V (45) 4画面に出力 21: goto #26 5ELSE句なのでGOTOで#26まで飛ぶ 24: aconst_null 6スタックにNULLを積む 25: astore_1 6 26: aload_1 7 27: ifnonnull #38 7コードは「a == null」だが最適化されたのかNotNULLで飛んでいる 30: new <java.lang.Exception> (29) 8オブジェクトNewして 33: dup 8コピーして 34: invokespecial java.lang.Exception.<init> ()V (51) 8コンストラクタ呼び出し 37: athrow 8スローする 38: return 終わり
なるほど!IFってこんな風に内部はgotoで実現されているのか。
さらに調子に乗ってシンクロしてみた
public void doAction2() throws Exception { String b = "Gara"; 1 synchronized (b) { 2 b = "SIN"; 3 } 4 b = "END"; 5 }
public void doAction2() throws java.lang.Exception Code(max_stack = 2, max_locals = 3, code_length = 19) 0: ldc "Gara" (16) 1 2: astore_1 1 3: aload_1 1 4: dup 2 5: astore_2 2 6: monitorenter 2ロック取得!!! 7: ldc "SIN" (54) 3 9: astore_1 3 10: aload_2 3 11: monitorexit 4ロック解放!!! 12: goto #18 4正常終了時 15: aload_2 4エクセプションハンドラー部分 16: monitorexit 4例外時に解放 17: athrow 4 18: ldc "END" (56) 5 20: astore_1 21: return Exception handler(s) = From To Handler Type 7 12 15 <Any exception>(0) 15 17 15 <Any exception>(0)
シンクロの場合暗黙的に例外時の処理が追加されている。最初なんで追加されているのかわからなかった。
その他これを知ってると大体読める
- sipush :byte,shortをスタックに積む
- invokevirtual :メソッド呼び出し
- invokeinterface :インターフェース呼び出し
- invokespecial :親呼び出し(コンストラクタ呼び出しもこれ)
- invokestatic :Staticメソッド呼び出し
詳細はhttp://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3
参考
http://bcel.jakarta.jp/
http://bcel.sourceforge.net/listclass.java
http://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3
http://www.javareading.com/bof/javavm5.html
*1:Singltonのダブルチェックがアンチパターンなのが実感できた。http://www.ibm.com/developerworks/java/library/j-dcl.html