Javaの実行時中間言語。MSの中間言語(MS IL)と並んで有名な中間言語。
自分らの世代で考えると本当にマシン語/アセンブラと同じと考えてよい。基本命令さえ把握すれば全然普通に読める。
普段このバイトコードでプログラムを読む必要はまったくないが、気分的に「いざとなればこのレベルで読めば全部分かる」と思い込んでおけば後々の今風技術にもひるまずに済む。
一部高級言語/オブジェクト指向言語固有命令と思われるものは出てくるが、それはそういうものでJavaマシン側でほどよく処理してくれていると考えれば問題ない(monitor,throw,invokevirtualなど)。
基本仕様一覧
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html (英語が苦手でもアセンブラの理解があればGoogle翻訳などでどうにかなる)解析環境
- intelliJ IDEAにjclasslib Bytecode viewer (https://plugins.jetbrains.com/plugin/9248-jclasslib-bytecode-viewer) プラグインをインストールする。
- Java,Scala等でサンプルのjavaコードを書く。よくあるサンプルプロジェクトみたいなものでもよい。
- 実行する(つまりjarがビルドされている)
- 該当ソースを選んで開く。その状態でメニュー View > Show bytecode with jclasslib を選ぶ。
- 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 (戻る)
完全にスタックベースで計算するアセンブラである。
これでわかったつもりになって問題ない。