JVMバイトコードは、 Java仮想マシン(JVM)の命令セットアーキテクチャ(ISA)であり、 Javaやその他のJVM互換ソースコードがコンパイルされる言語です。[ 1 ]各命令は1バイトで表現されるため、バイトコードと呼ばれ、コンパクトなデータ形式となっています。[ 2 ]
仮想マシンとバイトコードの性質上、JVMバイトコードプログラムは、ソースコードからコンパイルするという長いプロセスを経ることなく、互換性のあるJVMがインストールされているどのマシンでも実行可能です。
JVMバイトコードは、実行時にJVMによって解釈されるか、ジャストインタイム(JIT)コンパイルによってマシンコードにコンパイルされ、ネイティブアプリケーションとして実行されます。
JVMバイトコードはクロスプラットフォームのソフトウェア互換性とセキュリティのために設計されているため、JVMバイトコードアプリケーションはさまざまなハードウェアおよびソフトウェア構成で一貫して動作する傾向があります。[ 3 ]
一般的に、JavaプログラマーはJVM バイトコードを理解する必要も、それを認識する必要もありません。しかし、IBM developerWorks 誌で示唆されているように、「バイトコードと、 Java コンパイラによって生成される可能性のあるバイトコードを理解することは、アセンブリ言語の知識がCまたはC++プログラマーに役立つのと同様に、Java プログラマーに役立ちます。」[ 4 ]
バイトコードは、データ操作、制御転送、オブジェクトの作成と操作、メソッド呼び出しなど、さまざまな命令タイプで構成されており、これらはすべてJavaのオブジェクト指向プログラミングモデルに不可欠な要素です。[ 1 ]
JVMはスタックマシンとレジスタマシンの両方です。メソッド呼び出しの各フレームには、「オペランドスタック」と「ローカル変数」の配列があります。 [ 5 ] : 2.6 [ 2 ]オペランドスタックは、計算にオペランドを渡したり、呼び出されたメソッドの戻り値を受け取るために使用されます。一方、ローカル変数はレジスタと同じ目的で使用され、メソッド引数を渡すためにも使用されます。コンパイラによって計算されるオペランドスタックとローカル変数配列の最大サイズは、各メソッドの属性の一部です。[ 5 ] : 4.7.3それぞれは、0から65535まで独立してサイズを設定できます。各値は32ビットです。64ビットlongのdouble型は、2つの連続するローカル変数[ 5 ] : 2.6.1(ローカル変数配列では64ビットアラインメントされている必要はありません)またはオペランドスタックの1つの値(スタックの深さでは2つの単位としてカウントされます)を占有します。[ 5 ] : 2.6.2
各バイトコードは、オペコードを表す1バイトと、オペランドを表す0バイト以上のバイトから構成される。 [ 5 ]: 2.11
2015年時点で、可能な256バイト長のオペコードのうち、202 は使用中 (約 79%)、51 は将来の使用のために予約済み (約 20%)、3 命令 (約 1%) は JVM 実装で使用するために永久に予約されています。[ 5 ] : 6.2これらの 2 つ (impdep1およびimpdep2) は、それぞれ実装固有のソフトウェアとハードウェアのトラップを提供するために使用されます。3 つ目は、デバッガがブレークポイントを実装するために使用されます。
指示は大きくいくつかのグループに分類されます。
aload_0:)istoreladd:)fcmpli2b例d2i:)new、putfield)swap:)dup2ifeq例goto:)invokespecial:)areturn例外処理や同期処理など、より専門的なタスクに関する手順もいくつか記載されています。
多くの命令には、操作対象となるオペランドの型を示す接頭辞や接尾辞が付いています。[ 5 ]: 2.11.1これらは以下のとおりです。
例えば、iaddは2つの整数を加算し、 はdadd2つの倍精度浮動小数点数を加算します。const、load、 およびstore命令には、 の形式の接尾辞が付く場合もあります。ここで、および の場合は、 nは0~3の数値です。のnの最大値は型によって異なります。_nloadstoreconst
命令constは、指定された型の値をスタックにプッシュします。たとえば、iconst_5は値 5 の整数 (32 ビット値) をスタックにプッシュし、 はdconst_1値 1 の double (64 ビット浮動小数点値) をスタックにプッシュします。 にはaconst_null、参照をプッシュする もありますnull。および命令のnは、ローカル変数配列のロード元または格納先のインデックスを指定します。 命令は、ローカル変数 0 のオブジェクトをスタックにプッシュします (通常はオブジェクトです)。 は、loadスタックの最上位の整数をローカル変数 1 に格納します。 3 を超えるローカル変数の場合、サフィックスは削除され、オペランドを使用する必要があります。storeaload_0thisistore_1
以下の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コンパイラは上記の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 : returnJava仮想マシンをターゲットとし、JVMバイトコードを生成する最も一般的な言語はJavaです。当初は、 JavaソースコードをJVMバイトコードにコンパイルするコンパイラはSun Microsystemsのjavacコンパイラのみでしたが、現在ではJVMバイトコードのすべての仕様が利用可能になったため、他の企業もJVMバイトコードを生成するコンパイラを提供しています。他のコンパイラの例としては、以下のようなものがあります。
一部のプロジェクトでは、JVMバイトコードを手動で記述できるようにJavaアセンブラを提供しています。アセンブリコードは、例えばJava仮想マシンをターゲットとするコンパイラなどによって、マシンによって生成される場合もあります。代表的なJavaアセンブラには以下のようなものがあります。
その他にも、Java仮想マシンをターゲットとする様々なプログラミング言語向けのコンパイラが開発されている。例えば、以下のようなものがある。
現在、JVMバイトコードを実行できるJava仮想マシンは、無料製品と商用製品の両方を含め、数多く存在します。仮想マシン内でバイトコードを実行することが望ましくない場合は、開発者はGNU Compiler for Java (GCJ)などのツールを使用して、Javaソースコードまたはバイトコードを直接ネイティブマシンコードにコンパイルすることもできます。一部のプロセッサは、JVMバイトコードをネイティブに実行できます。このようなプロセッサはJavaプロセッサと呼ばれます。
Java仮想マシンは、動的型付け言語をある程度サポートしています。既存のJVM命令セットのほとんどは静的型付けです。つまり、メソッド呼び出しのシグネチャはコンパイル時に型チェックされ、この決定をランタイムに延期したり、別の方法でメソッドディスパッチを選択したりするメカニズムはありません。 [ 12 ]
Java Specification Request ( JSR ) 292 ( Supporting Dynamically Typed Languages on the Java Platform ) [ 13 ]invokedynamicでは、JVM レベルで新しい命令が追加され、既存の静的型チェック命令の代わりに動的型チェックに依存するメソッド呼び出しが可能になりました。Da Vinci Machineは、動的言語をサポートすることを目的とした JVM 拡張機能をホストするプロトタイプの仮想マシン実装です。Java Platform, Standard Edition (JSE) 7 をサポートするすべての JVM には、このオペコードも含まれています。invokevirtualinvokedynamic