Javaバイトコード
Javaバイトコードは、Java仮想マシン(Java VM, JVM)が実行する命令形式である。各バイトコードのオペコードは長さが1バイトであるが、引数を持つものもあるため、結果として複数バイトの命令となる。256個のオペコードの全てが使われているのではなく、51個が将来のために予約されている。Javaプラットフォームの開発元であるサン・マイクロシステムズは、205個のオペコードのうち、3つのコードをJVM実装による内部使用のために予約するものとした。もしJVMの命令セットが将来的に拡張されたとしても、これらの予約されたオペコードは使われないことが保証されている(有効なJavaクラスファイルの中には出現しない)[1]。
Javaとの関係
[編集]Javaプログラマは、Javaバイトコードについて知ったり理解したりする必要は全くない。しかしながら、IBMのdeveloperWorksに投稿された記事では、「バイトコードを理解することと、どんなバイトコードがJavaコンパイラにより生成される可能性が高いかを理解することは、アセンブラ(アセンブリ言語)の知識がCやC++プログラマの助けになるのと同じように、Javaプログラマの助けになる」と述べられている[2][3][4]。
命令
[編集]各バイトは256個の値を持ちうるので、256個のオペコードが可能である。これらのうち、0x00から0xcaまでの範囲と、0xfe、0xffが割り当てられた値である。0xcaはデバッガ用のブレークポイント命令として予約されており、Java言語は使用しない。同様に、0xfeおよび0xffは実装固有の機能に対するバックドアまたはトラップを提供することを意図して予約されており、Java言語は使用しない。
命令はいくつかの大まかな分類に分けられる:
- ロードおよびストア(例: aload_0, istore)
- 算術および論理(例: ladd, fcmpl)
- 型変換(例: i2b, d2i)
- オブジェクト作成および操作(new, putfield)
- オペランドスタック管理(例: swap, dup2)
- 制御の移動(例: ifeq, goto)
- メソッド呼び出しおよび復帰(例: invokespecial, areturn)
例外のスローやスレッドの同期などのように、多くのより専門化されたタスクのための命令もいくつか存在する。
多くの命令は、扱うオペランドの型を示す接頭辞や接尾辞を持つ。これらは以下の通りである。
接頭辞 / 接尾辞 | オペランド型 |
---|---|
i | integer |
l | long |
s | short |
b | byte |
c | character |
f | float |
d | double |
z | boolean |
a | reference |
例えば、"iadd" は2つのintegerを加算し、"dadd" は2つのdoubleを加算する。"const"、"load"、そして "store" 命令は、"_n" という形式の接尾辞も取る。nは数字で、 "load" および "store" に対しては0から3までの値を取る。"const" に対してはnの最大値は型により違う。
"const" 命令はスタックに指定された型の値をプッシュする。例えば "iconst_5" は、integer 5をプッシュする。その一方、"dconst_1" はdouble 1をプッシュする。"null" をプッシュする "aconst_null" も存在する。"load" および "store" 命令用の nは、ロードやストアする変数テーブル[要説明]内の場所を指定する。"aload_0" 命令はスタックに変数0であるオブジェクト(このオブジェクトは通常 "this" オブジェクト)をプッシュする。"istore_1" はスタックのトップにあるintegerを変数1にストアする。より大きい数の変数に対しては、この形式の接尾辞は削除し、演算子を使用する必要がある。
計算モデル
[編集]Java仮想マシン(の計算モデル)は、いわゆるスタックマシンである。例として、次のようなx86のコードを考える:
mov eax, byte [ebp-4] mov edx, byte [ebp-8] add eax, edx mov ecx, eax
2つの値を加算して別の場所にその結果をコピーする。類似のJavaバイトコードは以下のようになる:
0 iload_1 1 iload_2 2 iadd 3 istore_3
ここで、加算される2つの値はスタックに積まれ、加算命令によりスタックから値が回収され、加算され、そして結果がスタックに戻される。それからストア命令がスタックのトップの値を変数の場所へ移動する。命令の前にある数は、メソッドの最初から各命令のオフセットを単に表しているだけである。このスタック指向モデルは、この言語のオブジェクト指向の側面にも及ぶ。例えば、"getName()" というメソッドの呼び出しは以下のようになる:
Method java.lang.String getName() 0 aload_0 // "this" オブジェクトが変数テーブルの場所0にストアされる。 1 getfield #5 <Field java.lang.String name> // この命令はスタックのトップからオブジェクトをポップし、 // そのオブジェクトから指定されたフィールドを取得し、 // そしてスタックにそのフィールドをプッシュする。 // この例では、"name" フィールドがこのクラスの定数プールの5番目に対応する。 4 areturn // メソッドからスタックのトップのオブジェクトを返す。
例
[編集]以下のJavaコードを考えよう:
outer: for (int i = 2; i < 1000; i++) { for (int j = 2; j < i; j++) { if (i % j == 0) continue outer; } System.out.println(i); }
上記がメソッド内に置かれていると仮定すると、Javaコンパイラは上記のJavaコードを以下のように翻訳するだろう:
0: iconst_2 1: istore_1 2: iload_1 3: sipush 1000 6: if_icmpge 44 9: iconst_2 10: istore_2 11: iload_2 12: iload_1 13: if_icmpge 31 16: iload_1 17: iload_2 18: irem 19: ifne 25 22: goto 38 25: iinc 2, 1 28: goto 11 31: getstatic #84; //フィールド java/lang/System.out:Ljava/io/PrintStream; 34: iload_1 35: invokevirtual #85; //メソッド java/io/PrintStream.println:(I)V 38: iinc 1, 1 41: goto 2 44: return
生成
[編集]Javaバイトコードを生成する、Java仮想マシンをターゲットとした最も一般的な言語はJavaである。元々は、サン・マイクロシステムズからのjavacというコンパイラのみが唯一の実装として存在していた。javacはJavaソースコードをJavaバイトコードへとコンパイルする。しかし現在[いつ?]ではJavaバイトコードに対するすべての仕様が利用可能であるため、他のパーティーがJavaバイトコードを生成するコンパイラを提供している。他のコンパイラの例は以下の通り:
- Jikes - JavaからJavaバイトコードへとコンパイルする(IBMにより開発され、C++で実装されている)
- Espresso - JavaからJavaバイトコードへとコンパイルする(Java 1.0のみ)
- GCJ - Java用GNUコンパイラで、JavaからJavaバイトコードへとコンパイルする。これはネイティブな機械語にコンパイルすることもでき、GNUコンパイラコレクション (GCC) の一部として利用可能である。
いくつかのプロジェクトは、手動でJavaバイトコードを書くことを可能とするためのJavaアセンブラを提供する。アセンブリコードは、Java仮想マシンをターゲットとするコンパイラによるものを例として、マシンによっても生成される。有名なJavaアセンブラは以下の通り:
- Jasmin - Java仮想マシン命令セットを利用するシンプルなアセンブリライクな構文規則で記述されたJavaクラス用のテキスト記述を得て、Javaクラスファイルを生成する[5]。
- Jamaica - Java仮想マシン用のマクロアセンブリ言語。Java構文規則はクラスやインタフェースのために利用される。メソッド本体はバイトコード命令を用いて指定される[6]。
その他にも、Java仮想マシンをターゲットとする、Javaとは異なるパラダイムを持つプログラミング言語用に開発されたコンパイラがある。それらは以下の通り:
- ColdFusion
- JRubyおよびJython - RubyとPythonを基盤とした2つのスクリプト言語
- Groovy - Javaを基盤とした汎用スクリプト言語
- Scala - オブジェクト指向および関数型プログラミングをサポートする、型セーフな多目的プログラミング言語
- JGNATおよびAppletMagic - AdaからJavaバイトコードへのコンパイルをする
- CからJavaバイトコードへのコンパイラ
- Clojure
- JavaFX ScriptコードもJavaバイトコードにコンパイルされる
- Kotlin
実行
[編集]JavaバイトコードはJava仮想マシン内で実行されるように設計されている。今日ではフリーおよび商用ともに様々な仮想マシンが存在する。
実行するJava仮想マシン内のJavaバイトコードが望ましくない場合、開発者はGCJのようなツールを使用することで、JavaソースコードやJavaバイトコードを直接ネイティブコードにコンパイルすることもできる。いくつかのプロセッサはJavaバイトコードをネイティブに実行することができる。そのようなプロセッサは「Javaプロセッサ」として知られている。
動的言語のサポート
[編集]Java仮想マシン (JVM) のJVM命令セットおよびメソッド呼び出し機構は、メソッド呼び出しのシグネチャをコンパイル時に型チェックする、静的型付けベースと言える。[7]
JSR 292(Java™ プラットフォーム上の動的型付け言語のサポート)[8]により、(静的型検査ベースのinvokevirtual
の代わりとして)動的型検査ベースの新規のinvokedynamic
命令が追加された。Da Vinci Machineは、動的言語サポート向けのJVM拡張をホストするプロトタイプ仮想マシン実装である。Java SE 7をサポートする全てのJVMにもinvokedynamic
命令が含まれる。
関連項目
[編集]- Javaクラスファイル
- Java仮想マシン
- 共通中間言語 (CIL)
脚注
[編集]- ^ VM Spec - Reserved Opcodes
- ^ Understanding bytecode makes you a better programmer[リンク切れ]
- ^ Java bytecode: Understanding bytecode makes you a better programmer | Peter Haggar, Internet Archive
- ^ A Formal Introduction to the Compilation of Java, Stephan Diehl, "Software - Practice and Experience", Vol. 28(3), pages 297-327, March 1998.
- ^ Jasminホームページ
- ^ Jamaica : Java仮想マシン (JVM) マクロアセンブラ
- ^ Nutter, Charles (2007年1月3日). “InvokeDynamic: Actually Useful?”. 2008年1月25日閲覧。
- ^ see JSR 292
外部リンク
[編集]- Java仮想マシン仕様
- Bytecode Visualizer - バイトコードビューワおよびデバッガ(Eclipseプラグイン)