JVMバイトコード

Javaの実行時中間言語。MSの中間言語(MS IL)と並んで有名な中間言語。

自分らの世代で考えると本当にマシン語/アセンブラと同じと考えてよい。基本命令さえ把握すれば全然普通に読める。

普段このバイトコードでプログラムを読む必要はまったくないが、気分的に「いざとなればこのレベルで読めば全部分かる」と思い込んでおけば後々の今風技術にもひるまずに済む。

一部高級言語/オブジェクト指向言語固有命令と思われるものは出てくるが、それはそういうものでJavaマシン側でほどよく処理してくれていると考えれば問題ない(monitor,throw,invokevirtualなど)。

  • 解析環境
  1. intelliJ IDEAにjclasslib Bytecode viewer (https://plugins.jetbrains.com/plugin/9248-jclasslib-bytecode-viewer) プラグインをインストールする。
  2. Java,Scala等でサンプルのjavaコードを書く。よくあるサンプルプロジェクトみたいなものでもよい。
  3. 実行する(つまりjarがビルドされている)
  4. 該当ソースを選んで開く。その状態でメニュー View > Show bytecode with jclasslib を選ぶ。
  5. jar内サマリーとそのクラスのバイトコード解析ウィンドウが出る。

開いているファイルのクラスのバイトコード一式の表示が出る

  • Constant Pool 定数一式
  • Field メンバー変数
  • Method メソッド定義 Code部にバイトコードがアセンブラ風に表示される

あたりが分かればあとは何かのバイトコードの仕様とアセンブラ知識があればなんとなくは分かる。

元Javaソースとバイトコード出力結果を並べてみる。

public class SimpleJava { 
  public void addTest(int num1,int num2)  {
    int sum = num1 + num2;
    System.out.println(Integer.toString(sum));
  }
}
(引数num1はローカル変数1に、num2はローカル変数2に入っているとする) 
0 iload_1    (ローカル変数1から整数を取り出し、スタックにプッシュする)
 1 iload_2   (ローカル変数2から整数を取り出し、スタックにプッシュする)
 2 iadd      (スタックに詰まれた2つのデータを2つプルして取り出し、それを整数として加算し、スタックに再プッシュする=つまりスタックは2つの数字の代わりに加算した数字になる)
 3 istore_3  (スタックから整数をプルし、ローカル変数3に入れる)
 4 getstatic #8 <java/lang/System.out> (外部変数System.out出力端末番号を取り出しスタックにプッシュする)
 7 iload_3   (ローカル変数3から数字を取り出し、スタックにプッシュする=この時点でスタックには、System.outと加算した数字が詰まれる)
 8 invokestatic #9 <java/lang/Integer.toString> (外部の手続きInteger.toStringを呼び出す。恐らく中でスタックをプルして加算した数字を取り出し、文字列配列変換して、そのポインタをプッシュする)
11 invokevirtual #10 <java/io/PrintStream.println> (外部の手続きprintlnを呼び出す。スタックに詰まれていたSystem.outと変換した文字列ポインタを取り出し、プリント処理をする)
14 return     (戻る)

完全にスタックベースで計算するアセンブラである。

これでわかったつもりになって問題ない。