目次
このドキュメントでは、抽象マシンを指定します。 Java Virtual Machineの特定の実装については説明しません。
Java Virtual Machineを正しく実装するには、classファイル形式を読み取って、そこで指定した操作を正しく実行することのみが可能です。 Java Virtual Machineの仕様に含まれない実装の詳細は、不必要に実装者の創造性を制限します。 たとえば、ランタイム・データ領域のメモリー・レイアウト、使用されるガベージ・コレクション・アルゴリズム、およびJava Virtual Machine命令の内部最適化(マシン・コードへの変換など)は、実装者の判断に任されます。
この仕様のUnicodeへのすべての参照は、The Unicode Standard、 Version 17.0.0 (https://www.unicode.org/から入手可能)に関して示されています。
classファイル形式
Java Virtual Machineによって実行されるコンパイル済コードは、ハードウェアおよびオペレーティング・システムに依存しないバイナリ形式を使用して表され、通常はclassファイル形式と呼ばれるファイルに格納されます(ただし、必ずしもそうではありません)。 classファイル形式は、プラットフォーム固有のオブジェクト・ファイル形式で付与されるバイト順序などの詳細を含め、クラスまたはインタフェースの表現を正確に定義します。
第4章「classファイル形式」では、classファイル形式について詳しく説明します。
Javaプログラミング言語と同様に、Java Virtual Machineは、プリミティブ型と参照型の2種類の型で動作します。 対応する値には、変数に格納され、引数として渡され、メソッドによって戻され、操作される2種類の値があります: プリミティブ値および参照値。
Java Virtual Machineでは、ほぼすべての型チェックが実行時間より前に(通常はコンパイラによって)実行され、Java Virtual Machine自体で実行する必要がないことを想定しています。 プリミティブ型の値は、実行時にその型を決定したり、参照型の値と区別するためにタグ付けしたり、検査したりする必要はありません。 かわりに、Java Virtual Machineの命令セットは、特定の型の値を操作するための命令を使用してオペランド型を区別します。 たとえば、iadd、ladd、faddおよびdaddはすべて、2つの数値を追加して数値結果を生成するJava Virtual Machine命令ですが、それぞれint、long、floatおよびdoubleというオペランド・タイプに特化しています。 Java Virtual Machine命令セットでの型サポートのサマリーは、§2.11.1を参照してください。
Java Virtual Machineには、オブジェクトに対する明示的なサポートが含まれています。 オブジェクトは、動的に割り当てられたクラス・インスタンスまたは配列です。 オブジェクトへの参照は、Java Virtual Machineタイプがreferenceであるとみなされます。 参照は多相です。1つの参照は、複数のクラス型、インタフェース型または配列型の値でもかまいません。 reference型の値は、オブジェクトへのポインタと考えることができます。 オブジェクトへの複数の参照が存在する可能性があります。 オブジェクトは常に、reference型の値を使用して操作、渡およびテストされます。
Java Virtual Machineでサポートされているプリミティブ・データ型は、数値型、boolean型(§2.3.4)およびreturnAddress型(§2.3.3)です。
数値型は、整数型(§2.3.1)と浮動小数点型(§2.3.2)で構成されます。
整数型は次のとおりです。
値が8ビット符号付き2の補完整数で、デフォルト値がゼロであるbyte
値が16ビット符号付き2の補完整数で、デフォルト値がゼロであるshort
値が32ビット符号付き2の補完整数で、デフォルト値がゼロであるint
値が64ビット符号付き2の補完整数で、デフォルト値がゼロであるlong
char。値は、Basic Multilingual PlaneのUnicodeコード・ポイントを表す16ビットの符号なし整数で、UTF-16でエンコードされ、デフォルト値はnullコード・ポイント('\\u0000')です。
浮動小数点型は次のとおりです。
値が32ビットIEEE 754 binary32形式で表現可能な値と正確に対応しており、デフォルト値が正のゼロであるfloat
値が64ビットIEEE 754 binary64形式の値に正確に対応しており、デフォルト値が正のゼロであるdouble
boolean型の値は、真理値trueおよびfalseをエンコードし、デフォルト値はfalseです。
Java®仮想マシン仕様の最初のエディションでは、booleanはJava Virtual Machineタイプとはみなされませんでした。 ただし、boolean値は、Java Virtual Machineでのサポートが制限されています。 Java®仮想マシン仕様の第2版では、booleanをタイプとして扱って問題を明確化しました。
returnAddress型の値は、Java Virtual Machine命令のopコードへのポインタです。 プリミティブ型では、returnAddress型のみがJavaプログラミング言語型に直接関連付けられません。
Java Virtual Machineの整数型の値は、次のとおりです。
byteの場合は、-128から127 (-27から27 - 1)まで(両端を含む)
shortの場合は、-32768から32767 (-215から215 - 1)まで(両端を含む)
intの場合は、-2147483648から2147483647 (-231から231 - 1)まで(両端を含む)
longの場合は、-9223372036854775808から9223372036854775807 (-263から263 - 1)まで(両端を含む)
charの場合、0から65535まで(両端を含む)
浮動小数点型は floatおよび doubleで、IEEE 754標準(JLS§1.7)で規定されているように、IEEE 754値および演算の32ビットbinary32および64ビットbinary64浮動小数点形式と概念的に関連付けられています。
Java SE 15以降では、Java Virtual Machineは2019バージョンのIEEE 754 Standardを使用します。 Java SE 15より前は、Java Virtual Machineは1985バージョンのIEEE 754 Standardを使用していました。ここで、binary32形式は単一形式と呼ばれ、binary64形式はdouble形式と呼ばれていました。
IEEE 754には、符号と大きさで構成される正と負の数だけでなく、正と負のゼロ、正と負の 無限、および特殊な Not-a-Number値(以降は NaN)も含まれています。 NaN値は、ゼロで除算するなど、特定の無効な操作の結果を表すために使用されます。 float型とdouble型の両方のNaN定数は、Float.NaNおよびDouble.NaNとして事前定義されています。
浮動小数点型の有限非ゼロ値は、すべて s ⋅ m ⋅ 2(e - N + 1)の形式で表すことができます。ここでは:
sは+1または-1です。
mは、2N未満の正の整数です。
eは、Emin = -(2K-1-2)からEmax = 2K-1-1までの整数です。
NおよびKは、型に依存するパラメータです。
一部の値は、この形式で複数の方法で表すことができます。 たとえば、浮動小数点型の値vが、s、mおよびeの特定の値を使用してこの形式で表される場合、mが偶数であり、eが2K-1未満であった場合は、mを停止し、eを1増やして、同じ値vの2番目の表現を生成できます。
この形式での表現は、正規化(m ≥ 2N-1の場合)と呼ばれ、それ以外の場合、表現は非正規と呼ばれます。 浮動小数点型の値がm ≥ 2N-1のように表現できない場合、その大きさは最小正規化値の大きさを下回るため、その値は非正規値であるとみなされます。
floatおよびdoubleのパラメータNおよびK (および導出パラメータEminおよびEmax)の制約を表2.3.2-Aにまとめます。
表2.3.2-A. 浮動小数点パラメータ
| Parameter | float |
double |
|---|---|---|
N |
24 | 53 |
| K | 8 | 11 |
| Emax | +127 | +1023 |
| Emin | -126 | -1022 |
NaNを除き、浮動小数点値は順序付けされます。 最小から最大まで配置すると、負の無限大、負の有限非ゼロ値、正と負のゼロ、正の有限非ゼロ値、正の無限大になります。
IEEE 754では、binary32およびbinary64浮動小数点形式ごとに複数の異なる NaN値を使用できます。 ただし、Java SE Platformでは、通常、特定の浮動小数点型のNaN値が単一の正規値に縮小されたかのように扱われるため、この仕様は通常、任意のNaNを正規値として参照します。
IEEE 754では、非 NaN引数を持つ浮動小数点演算が NaN結果を生成する可能性があります。 IEEE 754は一連の NaNビットパターンを指定しますが、NaN結果を表すためにどの特定の NaNビットパターンが使用されるかは指定しません。これはハードウェアアーキテクチャーに任されます。 プログラマは、遡及診断情報など、エンコードする様々なビット・パターンを持つNaNを作成できます。 これらのNaN値は、floatおよびdoubleのFloat.intBitsToFloatおよびDouble.longBitsToDoubleメソッドでそれぞれ作成できます。 逆に、NaN値のビット・パターンを検査するために、Float.floatToRawIntBitsおよびDouble.doubleToRawLongBitsメソッドをそれぞれfloatおよびdoubleに使用できます。
正のゼロと負のゼロの比較は等しくなりますが、それらを区別できる他の操作があります。たとえば、1.0を0.0で除算すると正の無限大になりますが、1.0を-0.0で除算すると負の無限大になります。
NaNは順序付けられていないため、数値等価に対する数値比較およびテストでは、オペランドのいずれかまたは両方がNaNの場合、値がfalseになります。 特に、値自体に対する数値等価性のテストは、値がNaNの場合にのみ、値falseを持ちます。 いずれかのオペランドがNaNの場合、数値不等式のテストは値trueを持ちます。
returnAddressのタイプと値
returnAddress型は、Java Virtual Machineのjsr、retおよびjsr_w命令で使用されます(§jsr、§ret、§jsr_w)。 returnAddress型の値は、Java Virtual Machine命令のopコードへのポインタです。 数値プリミティブ型とは異なり、returnAddress型はどのJavaプログラミング言語型にも対応せず、実行中のプログラムでは変更できません。
booleanタイプ
Java Virtual Machineではboolean型が定義されていますが、サポートは制限されています。 boolean値に対する操作専用のJava Virtual Machine命令はありません。 かわりに、boolean値を操作するJavaプログラミング言語の式は、Java Virtual Machineのintデータ型の値を使用するようにコンパイルされます。
Java Virtual Machineは、boolean配列を直接サポートします。 newarray命令(§newarray)により、boolean配列を作成できます。 boolean型の配列は、byte配列命令baloadおよびbastore (§baload、§bastore)を使用してアクセスおよび変更されます。
OracleのJava Virtual Machine実装では、Javaプログラミング言語のboolean配列は、boolean要素ごとに8ビットを使用して、Java Virtual Machineのbyte配列としてエンコードされます。
Java Virtual Machineでは、1を使用してboolean配列コンポーネントをエンコードし、trueおよび0を表現してfalseを表します。 Javaプログラミング言語のboolean値がコンパイラによってJava Virtual Machineタイプintの値にマップされる場合、コンパイラは同じエンコーディングを使用する必要があります。
reference型には、クラス型、配列型およびインタフェース型の3種類があります。 これらの値は、動的に作成されたクラス・インスタンス、配列、またはインタフェースを実装するクラス・インスタンスまたは配列への参照です。
配列型は、単一のディメンションを持つコンポーネント型で構成されます(長さは型によって指定されません)。 配列型のコンポーネント・タイプは、それ自体が配列型である場合があります。 配列型から開始してそのコンポーネント型が考慮され、(配列型でもある場合)その型のコンポーネント型が考慮される場合、最終的に配列型ではないコンポーネント型に到達する必要があります。これは、配列型の要素型と呼ばれます。 配列型の要素型は、必ずしもプリミティブ型、クラス型、またはインタフェース型のいずれかです。
reference値は、オブジェクトなしへの参照である特別なnull参照でもかまいません。これはnullによってここに示されます。 null参照には、最初はランタイム型はありませんが、任意の型にキャストできます。 referenceタイプのデフォルト値はnullです。
この仕様では、具体的な値エンコーディングnullは指定されていません。
Java Virtual Machineは、プログラムの実行時に使用される様々なランタイム・データ領域を定義します。 これらのデータ領域の一部は、Java Virtual Machineの起動時に作成され、Java Virtual Machineが終了したときにのみ破棄されます。 その他のデータ領域はスレッド単位です。 スレッドごとのデータ領域は、スレッドが作成され、スレッドが終了したときに破棄されるときに作成されます。
pcレジスタ
Java Virtual Machineは、多数の実行スレッドを一度にサポートできます(JLS§17)。 各Java Virtual Machineスレッドには、独自のpc (プログラム・カウンタ)レジスタがあります。 どの時点においても、各Java Virtual Machineスレッドは単一のメソッドのコード、つまりそのスレッドの現在のメソッド(§2.6)を実行しています。 そのメソッドがnativeでない場合、pcレジスタには現在実行されているJava Virtual Machine命令のアドレスが含まれます。 スレッドによって現在実行されているメソッドがnativeの場合、Java Virtual Machineのpcレジスタの値は未定義です。 Java Virtual Machineのpcレジスタの幅は、特定のプラットフォームでreturnAddressまたはネイティブ・ポインタを保持するのに十分です。
各Java Virtual Machineスレッドには、スレッドと同時に作成されたプライベートなJava Virtual Machineスタックがあります。 Java Virtual Machineスタックには、フレームが格納されます(§2.6)。 Java Virtual Machineスタックは、Cのような従来の言語のスタックに似ています。Cスタックは、ローカル変数と部分的な結果を保持し、メソッドの呼出しとリターンで役割を果たします。 Java Virtual Machineスタックは、プッシュ・フレームとポップ・フレーム以外は直接操作されないため、フレームが割り当てられることがあります。 Java Virtual Machineスタックのメモリーは連続している必要はありません。
Java®仮想マシン仕様の最初のエディションでは、Java Virtual MachineスタックはJavaスタックと呼ばれていました。
この仕様では、Java Virtual Machineスタックを固定サイズにするか、計算で必要とされるとおりに動的に拡張および縮小できます。 Java Virtual Machineスタックのサイズが固定の場合、各Java Virtual Machineスタックのサイズは、そのスタックの作成時に個別に選択できます。
Java Virtual Machineの実装では、プログラマまたはユーザーがJava Virtual Machineスタックの初期サイズを制御できるだけでなく、Java Virtual Machineスタックの動的な拡張または縮小の場合は、最大サイズと最小サイズを制御できます。
Java Virtual Machineスタックには、次の例外条件が関連付けられています。
スレッド内の計算で、許可されているよりも大きいJava Virtual Machineスタックが必要な場合、Java Virtual MachineはStackOverflowErrorをスローします。
Java Virtual Machineスタックを動的に拡張でき、拡張が試行されたが、拡張に影響を与えるために十分なメモリーを使用できないか、または新しいスレッドの初期Java Virtual Machineスタックを作成するために使用できるメモリーが不足している場合、Java Virtual MachineはOutOfMemoryErrorをスローします。
Java Virtual Machineには、すべてのJava Virtual Machineスレッド間で共有されるヒープがあります。 ヒープは、すべてのクラス・インスタンスおよび配列のメモリーが割り当てられるランタイム・データ領域です。
ヒープは仮想マシンの起動時に作成されます。 オブジェクトのヒープ・ストレージは、自動ストレージ管理システム(ガベージ・コレクタ)によって再利用されます。オブジェクトの割当ては明示的に解除されません。 Java Virtual Machineでは、特定のタイプの自動ストレージ管理システムは想定されず、実装者のシステム要件に従ってストレージ管理手法を選択できます。 ヒープは、固定サイズの場合もあれば、計算で要求されるとおりに拡張される場合もあり、より大きなヒープが不要になった場合は縮小される場合もあります。 ヒープのメモリーは連続している必要はありません。
Java Virtual Machine実装は、ヒープの初期サイズをプログラマまたはユーザーが制御できるだけでなく、ヒープを動的に拡張または縮小できる場合は、最大および最小のヒープ・サイズを制御できます。
ヒープには、次の例外条件が関連付けられます。
自動ストレージ管理システムで使用可能にできるより多くのヒープを計算する必要がある場合、Java Virtual MachineはOutOfMemoryErrorをスローします。
Java Virtual Machineには、すべてのJava Virtual Machineスレッド間で共有されるメソッド領域があります。 メソッド領域は、従来の言語のコンパイル済コードの格納領域、またはオペレーティング・システム・プロセスのテキスト・セグメントに類似しています。 ランタイム定数プール、フィールドおよびメソッド・データなどのクラスごとの構造、およびクラスおよびインタフェースの初期化およびインスタンス初期化(§2.9)で使用される特別なメソッドを含むメソッドおよびコンストラクタのコードが格納されます。
方法領域は仮想マシンの起動時に作成されます。 メソッド領域は論理的にヒープの一部ですが、単純な実装ではガベージ・コレクションまたは圧縮のいずれも選択しない場合があります。 この仕様では、コンパイル済コードの管理に使用されるメソッド領域またはポリシーの場所は指定されません。 メソッド領域は固定サイズの場合もあれば、計算によって必要に応じて拡張される場合もあり、より大きなメソッド領域が不要になった場合は縮小される場合があります。 メソッド領域のメモリーは連続している必要はありません。
Java Virtual Machine実装では、プログラマまたはユーザーがメソッド領域の初期サイズを制御できるだけでなく、メソッド領域のサイズが変化した場合に、メソッド領域の最大サイズと最小サイズを制御できます。
メソッド領域には、次の例外条件が関連付けられています。
メソッド領域のメモリーが割当てリクエストを満たすために使用可能にできない場合、Java Virtual MachineはOutOfMemoryErrorをスローします。
実行時定数プールは、classファイル(§4.4)内のconstant_pool表のクラスごとまたはインタフェースごとのランタイム表現です。 これには、コンパイル時に認識される数値リテラルから、実行時に解決する必要があるメソッドおよびフィールド参照まで、様々な種類の定数が含まれています。 ランタイム定数プールは、従来のプログラミング言語のシンボルテーブルと同様の関数を提供しますが、一般的なシンボルテーブルよりも広い範囲のデータが含まれています。
各実行時定数プールは、Java Virtual Machineのメソッド領域(§2.5.4)から割り当てられます。 クラスまたはインタフェースの実行時定数プールは、Java Virtual Machineによってクラスまたはインタフェースが作成されたときに構築されます(§5.3)。
次の例外条件は、クラスまたはインタフェースの実行時定数プールの構成に関連付けられます。
クラスまたはインタフェースの作成時に、実行時定数プールの構築にJava Virtual Machineのメソッド領域で使用可能なメモリーよりも多くのメモリーが必要な場合、Java Virtual MachineはOutOfMemoryErrorをスローします。
ランタイム定数プールの構成については、§5 (Loading、 Linking、 and Initializing)を参照してください。
Java Virtual Machineの実装では、Cスタックと呼ばれる従来のスタックを使用して、nativeメソッド(Javaプログラミング言語以外の言語で記述されたメソッド)をサポートできます。 ネイティブ・メソッド・スタックは、Cなどの言語でのJava Virtual Machineの命令セットのインタプリタの実装でも使用できます。 nativeメソッドをロードできず、それ自体が従来のスタックに依存しないJava Virtual Machine実装は、ネイティブ・メソッド・スタックを提供する必要はありません。 指定した場合、ネイティブ・メソッド・スタックは通常、各スレッドの作成時にスレッドごとに割り当てられます。
この仕様により、ネイティブ・メソッド・スタックを固定サイズにするか、計算で必要とされるとおりに動的に拡張および縮小できます。 ネイティブ・メソッド・スタックのサイズが固定の場合、各ネイティブ・メソッド・スタックのサイズは、そのスタックの作成時に個別に選択できます。
Java Virtual Machine実装では、プログラマまたはユーザーがネイティブ・メソッド・スタックの初期サイズを制御できるだけでなく、サイズが変化するネイティブ・メソッド・スタックの場合は、メソッド・スタックの最大サイズと最小サイズを制御できます。
ネイティブ・メソッド・スタックには、次の例外条件が関連付けられます。
スレッド内の計算で、許可されているよりも大きいネイティブ・メソッド・スタックが必要な場合、Java Virtual MachineはStackOverflowErrorをスローします。
ネイティブ・メソッド・スタックを動的に拡張でき、ネイティブ・メソッド・スタックの拡張が試行されたが、メモリーが不足して使用可能になった場合、または新しいスレッドの初期ネイティブ・メソッド・スタックの作成に十分なメモリーを使用できない場合、Java Virtual MachineはOutOfMemoryErrorをスローします。
フレームは、データおよび部分的な結果の格納、および動的リンク、メソッドの戻り値およびディスパッチ例外の実行に使用されます。
メソッドが呼び出されるたびに、新しいフレームが作成されます。 フレームは、その完了が正常であるか突然であるかに関係なく、メソッドの起動が完了すると破棄されます(捕捉されない例外がスローされます)。 フレームは、フレームを作成するスレッドのJava Virtual Machineスタック(§2.5.2)から割り当てられます。 各フレームには、独自のローカル変数配列(§2.6.1)、独自のオペランド・スタック(§2.6.2)、および現在のメソッドのクラスの実行時定数プール(§2.5.5)への参照があります。
フレームは、デバッグ情報などの追加の実装固有の情報で拡張できます。
ローカル変数配列とオペランド・スタックのサイズはコンパイル時に決定され、フレームに関連付けられたメソッドのコードとともに提供されます(§4.7.3)。 したがって、フレーム・データ構造のサイズはJava Virtual Machineの実装にのみ依存し、メソッドの起動時にこれらの構造のメモリーを同時に割り当てることができます。
実行中のメソッドのフレームである1つのフレームのみが、特定の制御スレッド内の任意の時点でアクティブになります。 このフレームは現在のフレームと呼ばれ、そのメソッドは現在のメソッドと呼ばれます。 現在のメソッドが定義されているクラスは、現在のクラスです。 ローカル変数およびオペランドスタックに対する操作は、通常、現在のフレームを参照します。
フレームは、そのメソッドが別のメソッドを呼び出す場合や、そのメソッドが完了した場合、カレントになります。 メソッドが呼び出されると、新しいフレームが作成され、制御が新しいメソッドに転送されると現在のフレームになります。 メソッドが戻ると、現在のフレームはそのメソッド呼出しの結果(ある場合)を前のフレームに戻します。 現在のフレームは、前のフレームが現在のフレームになると破棄されます。
スレッドによって作成されたフレームは、そのスレッドに対してローカルであり、他のスレッドからは参照できないことに注意してください。
各フレーム(§2.6)には、その局所変数と呼ばれる変数の配列が含まれています。 フレームのローカル変数配列の長さは、コンパイル時に決定され、フレームに関連付けられたメソッドのコードとともに、クラスまたはインタフェースのバイナリ表現で提供されます(§4.7.3)。
単一のローカル変数は、int、float、referenceまたはreturnAddress型の値を保持できます。 ローカル変数のペアは、long型またはdouble型の値を保持できます。
ローカル変数は、索引付けによって対処されます。 最初のローカル変数のインデックスは0です。 整数がゼロからローカル変数配列のサイズより1小さい場合にのみ、その整数がローカル変数配列への索引とみなされます。
long型またはdouble型の値は、連続する2つのローカル変数を占有します。 このような値は、より小さい索引を使用してのみ対処できます。 たとえば、索引nのローカル変数配列に格納されたdouble型の値は、索引nおよびn+1を持つローカル変数を実際に占有しますが、索引n+1のローカル変数はロードできません。 に保存できます。 ただし、これを行うと、ローカル変数nの内容が無効になります。
Java Virtual Machineでは、nを偶数にする必要はありません。 直感的な用語では、long型とdouble型の値をローカル変数配列に64ビット整列させる必要はありません。 実装者は、値用に予約された2つのローカル変数を使用して、そのような値を表す適切な方法を自由に決定できます。
Java Virtual Machineでは、ローカル変数を使用してメソッドの起動時にパラメータを渡します。 クラス・メソッドの起動時に、すべてのパラメータがローカル変数0から始まる連続したローカル変数で渡されます。 インスタンス・メソッドの呼出しでは、ローカル変数0が常に使用され、インスタンス・メソッドが呼び出されるオブジェクト(Javaプログラミング言語のthis)への参照が渡されます。 その後、すべてのパラメータは、ローカル変数1から始まる連続したローカル変数で渡されます。
各フレーム(§2.6)には、そのオペランド・スタックと呼ばれる最後の先入れ先出し(LIFO)スタックが含まれています。 フレームのオペランドスタックの最大深度はコンパイル時に決定され、フレームに関連付けられたメソッドのコードとともに提供されます(§4.7.3)。
コンテキストによって明確である場合、現在のフレームのオペランドスタックを単にオペランドスタックと呼びます。
オペランドスタックは、それを含むフレームが作成されると空になります。 Java Virtual Machineは、ローカル変数またはフィールドからオペランド・スタックに定数または値をロードする命令を提供します。 その他のJava Virtual Machine命令は、オペランド・スタックからオペランドを取り出して操作し、結果をオペランド・スタックにプッシュして戻します。 オペランド・スタックは、メソッドに渡されるパラメータを準備したり、メソッド結果を受信するためにも使用されます。
たとえば、iadd命令(§iadd)は、2つのint値を加算します。 追加するint値は、前の手順でプッシュされたオペランド・スタックの上位2つの値である必要があります。 両方のint値がオペランド・スタックからポップされます。 追加され、合計がオペランド・スタックに押し戻されます。 サブ計算をオペランド・スタックにネストすると、包含する計算で使用できる値になります。
オペランド・スタックの各エントリは、long型またはdouble型の値を含む、任意のJava Virtual Machine型の値を保持できます。
オペランドスタックからの値は、その型に適した方法で操作する必要があります。 たとえば、2つのint値をプッシュしてからlongとして処理したり、2つのfloat値をプッシュしてからiadd命令で追加することはできません。 少数のJava Virtual Machine命令(dup命令(§dup)およびswap (§swap))は、特定の型に関係なくランタイム・データ領域をRAW値として動作します。これらの命令は、個々の値を変更または分割するために使用できないような方法で定義されます。 オペランド・スタック操作に対するこれらの制限は、classファイル検証(§4.10)によって施行されます。
任意の時点で、オペランド・スタックに深さが関連付けられており、longまたはdouble型の値は深さに2つの単位を寄与し、他の型の値は1つの単位を寄与します。
各フレーム(§2.6)には、現在のメソッドのタイプに対する実行時定数プール(§2.5.5)への参照が含まれ、メソッド・コードの動的リンクをサポートします。 メソッドのclassファイル・コードは、呼び出されるメソッドと、シンボリック参照を介してアクセスされる変数を参照します。 動的リンクは、これらのシンボリック・メソッド参照を具象メソッド参照に変換し、必要に応じてクラスをロードして、まだ定義されていないシンボルを解決し、これらの変数の実行時位置に関連付けられたストレージ構造内の適切なオフセットに変数アクセスを変換します。
メソッドと変数のこの遅延バインディングにより、メソッドがこのコードを壊す可能性が低くなる他のクラスが変更されます。
メソッド呼出しは、例外(§2.10)がJava Virtual Machineから直接、または明示的なthrow文を実行した結果としてスローされない場合に、通常どおりに完了します。 現在のメソッドの呼出しが正常に完了した場合は、呼出し元メソッドに値が返されます。 これは、呼び出されたメソッドが戻り命令の1つ(§2.11.8)を実行するときに起こります。その選択は、返される値のタイプ(ある場合)に適している必要があります。
この場合、現在のフレーム(§2.6)を使用して、呼出し元の状態(ローカル変数およびオペランド・スタックを含む)をリストアし、呼出し元のプログラム・カウンタを適切に増やして、メソッド呼出し命令をスキップします。 その後、そのフレームのオペランド・スタックにプッシュされた戻り値(ある場合)を使用して、起動メソッドのフレーム内で正常に実行が続行されます。
Java Virtual Machineでは、オブジェクトの特定の内部構造は要求されません。
OracleのJava Virtual Machine実装の一部では、クラス・インスタンスへの参照は、それ自体がポインタのペアであるハンドルへのポインタです(1対の表)。オブジェクトのメソッドと、オブジェクトのタイプを表すClassオブジェクトへのポインタ、およびオブジェクト・データのヒープから割り当てられたメモリーへのポインタを含みます。
Java Virtual Machineには、IEEE 754標準(JLS§1.7)で指定された浮動小数点演算のサブセットが組み込まれています。
Java SE 15以降では、Java Virtual Machineは2019バージョンのIEEE 754 Standardを使用します。 Java SE 15より前は、Java Virtual Machineは1985バージョンのIEEE 754 Standardを使用していました。ここで、binary32形式は単一形式と呼ばれ、binary64形式はdouble形式と呼ばれていました。
算術(§2.11.3)および型変換(§2.11.4)に関するJava Virtual Machine命令の多くは、浮動小数点数で動作します。 これらの命令は通常、IEEE 754操作(Table 2.8-A)に対応していますが、次に説明する特定の命令は除きます。
表2.8-A. IEEE 754操作への対応
| 命令 | IEEE 754操作 |
|---|---|
| dcmp<op> (§dcmp<op>)、fcmp<op> (§fcmp<op>) | compareQuietLess, compareQuietLessEqual, compareQuietGreater, compareQuietGreaterEqual, compareQuietEqual, compareQuietNotEqual |
| dadd (§dadd)、fadd (§fadd) | 加算 |
| dsub (§dsub)、fsub (§fsub) | 減算 |
| dmul (§dmul)、fmul (§fmul) | 乗算 |
| ddiv (§ddiv)、fdiv (§fdiv) | division |
| dneg (§dneg)、fneg (§fneg) | negate |
| i2d (§i2d)、i2f (§i2f)、l2d (§l2d)、l2f (§l2f) | convertFromInt |
| d2i (§d2i)、d2l (§d2l)、f2i (§f2i)、f2l (§f2l) | convertToIntegerTowardZero |
| d2f (§d2f)、f2d (§f2d) | convertFormat |
Java Virtual Machineでサポートされている浮動小数点演算とIEEE 754標準との主な相違点は次のとおりです。
浮動小数点剰余命令 drem (§drem)および frem (§frem)は、IEEE 754剰余演算に対応していません。 この命令は、丸めゼロの丸めポリシーを使用する暗黙の除算に基づきます。代わりに、IEEE 754の余りは、丸めから最も近い丸めポリシーを使用する暗黙の除算に基づきます。 (端数処理ポリシーについては、後述します。)
浮動小数点否定命令 dneg (§dneg)および fneg (§fneg)は、IEEE 754否定演算に正確に対応していません。 特に、この命令は NaNオペランドの符号ビットを反転させる必要はありません。
Java Virtual Machineの浮動小数点命令は、例外、トラップ、または無効な操作のIEEE 754例外条件、ゼロによる除算、オーバーフロー、アンダーフロー、または不正確を通知しません。
Java Virtual Machineでは、IEEE 754シグナリング浮動小数点比較はサポートされておらず、シグナリングNaN値はありません。
IEEE 754には、Java Virtual Machineの丸めポリシーに対応しない丸め方向属性が含まれています。 Java Virtual Machineは、特定の浮動小数点命令で使用される丸めポリシーを変更するための手段を提供しません。
Java Virtual Machineは、IEEE 754で定義されたbinary32拡張およびbinary64拡張浮動小数点形式をサポートしていません。 浮動小数点値の操作時または格納時に、float型およびdouble型に指定された範囲を超えた拡張精度も使用することはできません。
Java Virtual Machineの対応する命令のない一部のIEEE 754操作は、IEEE 754 squareRoot操作のsqrtメソッド、IEEE 754 fusedMultiplyAdd操作のfmaメソッド、IEEE 754剰余操作のIEEEremainderメソッドなど、MathおよびStrictMathクラスのメソッドを介して提供されます。
Java Virtual Machineでは、IEEE 754の非正規浮動小数点数と段階的アンダーフローのサポートが必要です。これにより、特定の数値アルゴリズムの望ましいプロパティを簡単に証明できます。
浮動小数点演算は、実際の演算への近似です。 実数には無限数がありますが、特定の浮動小数点形式には有限数の値しかありません。 Java Virtual Machineでは、端数処理ポリシーは、特定の形式の実数から浮動小数点値へのマッピングに使用される関数です。 浮動小数点形式の表現可能な範囲の実数の場合、実数行の連続セグメントは単一の浮動小数点値にマップされます。 値が数値的に浮動小数点値に等しい実数は、その浮動小数点値にマップされます。たとえば、実数1.5は、指定された形式の浮動小数点値1.5にマップされます。 Java Virtual Machineでは、2つの丸めポリシーが次のように定義されています。
四捨五入の丸めポリシーは、(i)整数値への変換および(ii)剰余を除くすべての浮動小数点命令に適用されます。 四捨五入から最も近い端数処理ポリシーでは、正確な結果を無限に正確な結果に最も近い表現可能な値に丸める必要があります。最も近い2つの表現可能な値が等しく近い場合は、最下位ビットがゼロの値が選択されます。
最も近い丸めポリシーへの丸めは、IEEE 754、roundTiesToEvenのバイナリ演算のデフォルトの丸め方向属性に対応します。
roundTiesToEvenの丸め方向属性は、1985バージョンのIEEE 754 Standardでは「最も近い丸め」モードと呼ばれていました。 Java Virtual Machineの丸めポリシーは、この丸めモードに基づいて名前が付けられます。
ゼロに向けた丸めポリシーは、(i) d2i、d2l、f2i、および f2l命令(§d2i、§d2l、§f2i、§f2l)、および(ii)浮動小数点残り命令 dremおよび frem (§drem、§frem)による整数値への変換に適用されます。 ゼロの端数処理ポリシーへの丸めの下では、正確な結果は、無限に正確な結果よりも大きくない最も近い表現可能な値に丸められます。 整数に変換する場合、ゼロへの丸めポリシーは、小数桁ビットが破棄される切り捨てと同等です。
ゼロに向けた丸めポリシーは、IEEE 754でのバイナリ演算の roundTowardZero丸め方向属性に対応します。
roundTowardZeroの丸め方向属性は、IEEE 754 Standardの1985バージョンでは「ゼロに向かって丸める」丸めモードと呼ばれていました。 Java Virtual Machineの丸めポリシーは、この丸めモードに基づいて名前が付けられます。
Java Virtual Machineでは、すべての浮動小数点命令が浮動小数点結果を結果の精度に丸める必要があります。 各命令で使用される丸めポリシーは、前述のとおりに四捨五入するか、ゼロに四捨五入します。
Java 1.0および1.1では、浮動小数点式の厳密な評価が必要でした。 厳密な評価は、各floatオペランドがIEEE 754 binary32形式で表現可能な値に対応し、各doubleオペランドがIEEE 754 binary64形式で表現可能な値に対応し、対応するIEEE 754操作を持つ各浮動小数点演算子が同じオペランドのIEEE 754結果と一致することを意味します。
厳密な評価では予測可能な結果が得られますが、Java 1.0/1.1時代に共通するプロセッサ・ファミリのJava Virtual Machine実装でパフォーマンスの問題が発生しました。 その結果、Java 1.2からJava SE 16では、Java SE Platformにより、Java Virtual Machine実装で、各浮動小数点型に関連付けられた1つまたは2つの値セットを持つことができました。 float型はfloat値セットおよびfloat-extended-exponent値セットに関連付けられ、double型はダブル値セットおよびダブル拡張指数値セットに関連付けられました。 IEEE 754 binary32形式で表現可能な値に対応する浮動小数点値セット。浮動小数点拡張指数値セットの精度ビット数は同じですが、指数範囲が大きくなっています。 同様に、double値セットはIEEE 754 binary64形式で表現可能な値に対応しており、double-extended-exponent値セットの精度ビット数は同じですが、指数範囲は大きくなっています。 拡張指数値セットの使用をデフォルトで許可すると、一部のプロセッサファミリのパフォーマンスの問題が改善されました。
互換性のために、Java 1.2では、classファイルで拡張コンポーネント値セットの使用が実装で禁止されました。 classファイルは、メソッドの宣言にACC_STRICTフラグを設定することで、これを表現します。 ACC_STRICTは、floatオペランドに設定されたfloat値およびdoubleオペランドに設定されたdouble値を使用するように、メソッドの命令の浮動小数点セマンティクスを制限し、このような命令の結果が完全に予測可能であることを保証します。 したがって、ACC_STRICTとしてフラグ設定されたメソッドには、Java 1.0および1.1で指定されているのと同じ浮動小数点セマンティクスがありました。
Java SE 17以降では、Java SEプラットフォームは常に浮動小数点式の厳密な評価を必要とします。 厳密な評価を実装するパフォーマンスの問題が発生したプロセッサ・ファミリの新規メンバーは、これほど困難ではありません。 この指定では、前述の4つの値セットにfloatおよびdoubleが関連付けられなくなり、ACC_STRICTフラグが浮動小数点演算の評価に影響しなくなりました。 互換性のために、メジャー・バージョン番号が46から60のclassファイルでACC_STRICTを示すために割り当てられたビット・パターンは、メジャー・バージョン番号が60より大きいclassファイル(§4.6)では割当て解除されます(つまり、フラグは示されません)。 Java Virtual Machineの将来のバージョンでは、将来のclassファイルのビット・パターンに異なる意味を割り当てることができます。
クラスには、インスタンス初期化メソッドが0個以上あり、通常はJavaプログラミング言語で記述されたコンストラクタに対応します。
次のすべてに該当する場合、メソッドはインスタンス初期化メソッドです。
クラス(インタフェースではない)で定義されます。
特殊な名前<init>があります。
これはvoid (§4.3.3)です。
クラスでは、<init>という名前のvoid以外のメソッドはインスタンス初期化メソッドではありません。 インタフェースでは、<init>という名前のメソッドはインスタンス初期化メソッドではありません。 このようなメソッドは、Java Virtual Machine命令(§4.4.2、§4.9.2)によって起動できず、フォーマット・チェック(§4.6、§4.8)によって拒否されます。
インスタンス初期化メソッドの宣言と使用は、Java Virtual Machineによって制約されます。 この宣言では、メソッドのaccess_flags項目およびcode配列が制約されます(§4.6、§4.9.2)。 インスタンス初期化メソッドは、初期化されていないクラス・インスタンス(§4.10.1.9)のinvokespecial命令によってのみ起動できます。
<init>という名前はJavaプログラミング言語の有効な識別子ではないため、Javaプログラミング言語で記述されたプログラムで直接使用することはできません。
クラスまたはインタフェースには、最大1つのクラスまたはインタフェース初期化メソッドがあり、そのメソッドを起動するJava Virtual Machineによって初期化されます(§5.5)。
次のすべてに該当する場合、メソッドはクラスまたはインタフェースの初期化メソッドです。
classファイル内の<clinit>という名前の他のメソッドは、クラスまたはインタフェース初期化メソッドではありません。 これらは、Java Virtual Machine自体によって呼び出されることはなく、Java Virtual Machine命令(§4.9.1)によって呼び出されることもなく、フォーマット・チェック(§4.6、§4.8)によって拒否されることもあります。
<clinit>という名前はJavaプログラミング言語の有効な識別子ではないため、Javaプログラミング言語で記述されたプログラムで直接使用することはできません。
次のすべてに当てはまる場合、メソッドは署名多相です。
これは、java.lang.invoke.MethodHandleクラスまたはjava.lang.invoke.VarHandleクラスで宣言されます。
Object[]型の仮パラメータが1つあります。
ACC_VARARGSおよびACC_NATIVEフラグが設定されています。
Java Virtual Machineは、invokevirtual命令(§invokevirtual)内の署名多相メソッドに特別な処理を行い、メソッド・ハンドルの起動を有効にしたり、java.lang.invoke.VarHandleのインスタンスによって参照される変数へのアクセスを有効にします。
メソッド・ハンドルは、動的に厳密に型指定され、基礎となるメソッド、コンストラクタ、フィールドまたは同様の低レベル操作(§5.4.3.5)への直接実行可能な参照であり、引数または戻り値の変換はオプションです。 java.lang.invoke.VarHandleのインスタンスは、staticフィールド、非staticフィールド、配列要素、オフヒープ・データ構造のコンポーネントなど、変数または変数のファミリに対して動的に厳密に型指定された参照です。 詳細は、Java SE Platform APIのjava.lang.invokeパッケージを参照してください。
Java Virtual Machineの例外は、クラスThrowableまたはそのサブクラスの1つのインスタンスによって表されます。 例外をスローすると、例外がスローされた時点から、ローカルでない制御が即時に転送されます。
ほとんどの例外は、それらが発生したスレッドによるアクションの結果として同期的に発生します。 対照的に、非同期例外は、プログラムの実行のどの時点でも発生する可能性があります。 Java Virtual Machineは、次の3つの理由のいずれかで例外をスローします。
athrow命令(§athrow)が実行されました。
異常な実行条件がJava Virtual Machineによって同期的に検出されました。 これらの例外は、プログラムの任意のポイントでスローされるのではなく、次のいずれかの命令の実行後に同期的にスローされます。
例外を次のような結果として指定します。
命令がJavaプログラミング言語のセマンティクスに違反する操作(配列の範囲外の索引付けなど)を具現化する場合。
プログラムの一部のロードまたはリンクでエラーが発生した場合。
使用するメモリーが多すぎるなど、リソースの一部の制限を超えます。
Java Virtual Machineの実装で内部エラーが発生したため、非同期例外が発生しました(§6.3)。
Java Virtual Machine実装では、非同期例外がスローされる前に、少量の実行が制限される場合があります。 この遅延により、最適化されたコードが、Javaプログラミング言語のセマンティクスに従い、これらの例外を処理することが実用的なポイントで検出およびスローできるようになります。
単純な実装では、各制御転送命令の時点で非同期例外をポーリングする場合があります。 プログラムは有限サイズであるため、非同期例外を検出する際の合計遅延にバインドされます。 制御転送間で非同期例外が発生しないため、コード・ジェネレータには、パフォーマンス向上のために制御転送間で計算を並べ替える柔軟性があります。 Marc Feeley著『Polling Efficiently on Stock Hardware』、Proc. 1993 Conference on Functional Programming and Computer Architecture、デンマーク、コペンハーゲン、pp. 179-187がさらに読むことをお勧めします。
Java Virtual Machineによってスローされる例外は正確です。制御の転送が行われると、例外がスローされる時点より前に実行される命令のすべての影響が発生したように見える必要があります。 例外がスローされた時点より後に発生する命令は評価されていない可能性があります。 最適化されたコードが、例外が発生した時点に続く命令の一部を投機的に実行した場合、そのようなコードは、この投機的実行をプログラムのユーザーが表示できる状態から隠す準備をする必要があります。
Java Virtual Machineの各メソッドは、0個以上の例外ハンドラに関連付けることができます。 例外ハンドラは、例外ハンドラがアクティブなメソッドを実装するJava Virtual Machineコードへのオフセット範囲を指定し、例外ハンドラが処理できる例外のタイプを記述し、その例外を処理するコードの場所を指定します。 例外の原因となった命令のオフセットが例外ハンドラのオフセット範囲内にあり、例外タイプが例外ハンドラの処理する例外クラスの同じクラスまたはサブクラスである場合、例外は例外ハンドラと一致します。 例外がスローされると、Java Virtual Machineは現在のメソッド内で一致する例外ハンドラを検索します。 一致する例外ハンドラが見つかった場合、システムは一致したハンドラによって指定された例外処理コードに分岐します。
現在のメソッドでこのような例外ハンドラが見つからない場合、現在のメソッド呼出しは突然完了します(§2.6.5)。 異常終了時には、現在のメソッド呼出しのオペランド・スタックおよびローカル変数が破棄され、そのフレームがポップされて、呼出しメソッドのフレームが回復します。 次に、呼出し元のフレームなどのコンテキストで例外が再スローされ、メソッド呼出しチェーンが継続されます。 メソッド呼出しチェーンの最上位に到達する前に適切な例外ハンドラが見つからない場合は、例外がスローされたスレッドの実行が終了します。 スレッドが終了する前に、キャッチされない例外は次のルールに従って処理されます。
スレッドに捕捉されない例外ハンドラが設定されている場合、そのハンドラが実行されます。
それ以外の場合は、スレッドの親であるThreadGroupに対してメソッドuncaughtExceptionが呼び出されます。 ThreadGroupとその親ThreadGroupがuncaughtExceptionをオーバーライドしない場合、デフォルト・ハンドラのuncaughtExceptionメソッドが呼び出されます。
メソッドの例外ハンドラで一致が検索される順序は重要です。 classファイル内では、各メソッドの例外ハンドラが表に格納されます(§4.7.3)。 実行時に例外がスローされると、Java Virtual Machineは、現在のメソッドの例外ハンドラを、その表の先頭からclassファイルの対応する例外ハンドラ表に出現する順序で検索します。
Java Virtual Machineでは、メソッドの例外表エントリのネストや順序付けは強制されません。 Javaプログラミング言語の例外処理セマンティクスは、コンパイラ(§3.12)との連携によってのみ実装されます。 classファイルが他の方法で生成される場合、定義済の検索プロシージャによって、すべてのJava Virtual Machine実装が一貫して動作することが保証されます。
Java Virtual Machine命令は、実行する操作を指定する1バイトのopcodeと、その操作で使用される引数またはデータを提供する0個以上のオペランドで構成されます。 多くの命令にはオペランドがなく、オペコードのみで構成されます。
例外を無視すると、Java Virtual Machineインタプリタの内部ループは事実上
do {
atomically calculate pc and fetch opcode at pc;
if (operands) fetch operands;
execute the action for the opcode;
} while (there is more to do);
オペランドの数とサイズは、opcodeによって決まります。 オペランドがサイズが1バイトを超える場合、ビッグ・エンディアンの順序(上位バイトから順)で格納されます。 たとえば、ローカル変数への符号なし16ビット索引は、2つの符号なしバイト(byte1およびbyte2)として格納され、その値は(byte1 << 8) | byte2になります。
バイトコード命令ストリームは、単バイトのみ整列されます。 2つの例外は、lookupswitchおよびtableswitch命令(§lookupswitch、§tableswitch)で、一部のオペランドを4バイトの境界に強制的に配置するためにパディングされます。
Java Virtual Machineのopcodeをバイトに制限し、コンパイルされたコード内でデータの整列を行わないという決定は、圧縮性を優先する意識的なバイアスを反映しており、場合によっては、ナイーブ実装のパフォーマンスが低下します。 1バイトのopcodeも命令セットのサイズを制限します。 データの配置を前提としないということは、1バイトを超える即時データを、多数のマシンで実行時にバイトから構築する必要があることを意味します。
Java Virtual Machine命令セット内のほとんどの命令は、実行する操作に関する型情報をエンコードします。 たとえば、iload命令(§iload)は、ローカル変数の内容(intである必要がある)をオペランドスタックにロードします。 fload命令(§fload)は、float値でも同じことを行います。 この2つの命令は同一の実装を持つことができますが、異なるopcodeを持ちます。
型付き命令の大部分の場合、命令タイプはopcodeニーモニックでは、int操作の場合はi、longの場合はl、shortの場合はs、byteの場合はb、charの場合はc、floatの場合はf、doubleの場合はd、referenceの場合はaという文字で明示的に表されます。 型があいまいでない命令の中には、ニーモニックに型文字がないものがあります。 たとえば、arraylengthは常に、配列であるオブジェクトに対して動作します。 gotoなどの一部の命令は、無条件制御転送であり、型付きオペランドでは動作しません。
Java Virtual Machineの1バイトのOPコード・サイズを考慮すると、OPコードへのエンコーディング・タイプは、その命令セットの設計に圧力をかけることになります。 各型付き命令がJava Virtual Machineのすべてのランタイム・データ型をサポートしている場合、1バイトで表されるよりも多くの命令があります。 かわりに、Java Virtual Machineの命令セットによって、特定の操作に対するタイプ・サポートのレベルが低下します。 つまり、命令セットは意図的に直交的ではありません。 必要に応じて、サポートされていないデータ型とサポートされているデータ型を変換するために、個別の命令を使用できます。
表2.11.1-Aに、Java Virtual Machineの命令セットでの型サポートの概要を示します。 型情報を持つ特定の命令は、opcode列の命令テンプレートのTを型列の文字で置き換えることによって作成されます。 一部のインストラクション・テンプレートおよびタイプのタイプ列が空白の場合、そのタイプの操作をサポートするインストラクションは存在しません。 たとえば、int型のiloadのロード命令がありますが、byte型のロード命令はありません。
表2.11.1-Aのほとんどの手順には、整数型byte、charおよびshortのフォームがありません。 boolean型のフォームはありません。 型byteおよびshortの値がオペランド・スタックにロードされるたびに、符号拡張によってint型の値に暗黙的に変換されます。 同様に、boolean型およびchar型の値がオペランド・スタックにロードされるたびに、int型の値にゼロ拡張によって暗黙的に変換されます。 したがって、元々boolean型、byte型、char型およびshort型の変数に格納された値に対するほとんどの操作は、計算型intの値を操作する命令によって正しく実行されます。
表2.11.1-A. Java Virtual Machine命令セットの型サポート
| opcode | byte |
short |
int |
long |
float |
double |
char |
reference |
|---|---|---|---|---|---|---|---|---|
| Tipush | bipush | sipush | ||||||
| Tconst | iconst | lconst | fconst | dconst | aconst | |||
| Tload | iload | lload | fload | dload | aload | |||
| Tstore | istore | lstore | fstore | dstore | astore | |||
| Tinc | iinc | |||||||
| Taload | baload | saload | iaload | laload | faload | daload | caload | aaload |
| Tastore | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore |
| Tadd | iadd | ladd | fadd | dadd | ||||
| Tsub | isub | lsub | fsub | dsub | ||||
| Tmul | imul | lmul | fmul | dmul | ||||
| Tdiv | idiv | ldiv | fdiv | ddiv | ||||
| Trem | irem | lrem | frem | drem | ||||
| Tneg | ineg | lneg | fneg | dneg | ||||
| Tshl | ishl | lshl | ||||||
| Tshr | ishr | lshr | ||||||
| Tushr | iushr | lushr | ||||||
| Tand | iand | land | ||||||
| Tor | ior | lor | ||||||
| Txor | ixor | lxor | ||||||
| i2T | i2b | i2s | i2l | i2f | i2d | i2c | ||
| l2T | l2i | l2f | l2d | |||||
| f2T | f2i | f2l | f2d | |||||
| d2T | d2i | d2l | d2f | |||||
| Tcmp | lcmp | |||||||
| Tcmpl | fcmpl | dcmpl | ||||||
| Tcmpg | fcmpg | dcmpg | ||||||
| if_TcmpOP | if_icmpOP | if_acmpOP | ||||||
| Treturn | ireturn | lreturn | freturn | dreturn | areturn |
Java Virtual Machineのストレージ・タイプとJava Virtual Machineの計算タイプとのマッピングは、表2.11.1-Bにまとめます。
popやswapなどの特定のJava Virtual Machine命令は、型に関係なくオペランド・スタックで動作しますが、このような命令は、表2.11.1-Bに示されているように、特定のカテゴリの計算型の値にのみ使用するように制約されます。
表2.11.1-B. Java Virtual Machineのストレージおよび計算タイプ
| ストレージ・タイプ | 計算タイプ | カテゴリ |
|---|---|---|
boolean |
int |
1 |
byte |
int |
1 |
char |
int |
1 |
short |
int |
1 |
int |
int |
1 |
float |
float |
1 |
reference |
reference |
1 |
returnAddress |
returnAddress |
1 |
long |
long |
2 |
double |
double |
2 |
ロードおよび格納命令は、Java Virtual Machineフレーム(§2.6)のローカル変数(§2.6.1)とオペランド・スタック(§2.6.2)の間で値を転送します。
ローカル変数をオペランド・スタック(iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>)にロードします。
オペランド・スタックからローカル変数(istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>)に値を格納します。
オペランド・スタック(bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>)に定数をロードします。
より広い索引を使用して、またはより大規模な即時オペランド(wide)にアクセスできるようにする。
オブジェクトのフィールドや配列の要素にアクセスする命令(§2.11.5)もオペランドスタックとの間でデータを転送します。
iload_<n>、iload_0、iload_1、iload_2およびiload_3 (iload_<n>の場合)のメンバーを含む命令のファミリを示す、前述の手順のニーモニック。 このような命令ファミリは、1つのオペランドを取る追加の汎用命令(iload)の特殊化です。 特殊な命令の場合、オペランドは暗黙的であり、格納またはフェッチする必要はありません。 それ以外の場合、セマンティクスは同じです(iload_0は、オペランド 0を持つ iloadと同じことを意味します)。 山カッコ間の文字は、その命令ファミリの暗黙オペランドのタイプを指定します。<n>の場合は負でない整数、<i>の場合はint、<l>の場合はlong、<f>の場合はfloat、<d>の場合はdoubleです。
命令ファミリのこの表記は、この仕様全体で使用されます。
算術命令は、通常、オペランドスタック上の2つの値の関数である結果を計算し、その結果をオペランドスタックにプッシュバックします。 算術命令には、整数値を操作する命令と浮動小数点値を操作する命令の2種類があります。 これらの各種類の算術命令は、Java Virtual Machineの数値型に特化されています。 byte、shortおよびchar型の値(§2.11.1)またはboolean型の値に対する整数演算の直接的なサポートはありません。これらの演算は、int型で動作する命令によって処理されます。 整数命令と浮動小数点命令は、オーバーフローおよびゼロ除算での動作も異なります。 算術命令は次のとおりです。
iadd、ladd、fadd、daddを追加します。
減算: isub、lsub、fsub、dsub。
乗算: imul、lmul、fmul、dmul。
除算: idiv、ldiv、fdiv、ddiv。
残り: irem、lrem、frem、drem。
否定: ineg、lneg、fneg、dneg。
シフト: ishl、ishr、iushr、lshl、lshr、lushr。
ビット単位のOR: ior、lor。
ビット単位のAND: iand、land。
ビット単位の排他OR: ixor、lxor。
ローカル変数の増分: iinc。
比較: dcmpg、dcmpl、fcmpg、fcmpl、lcmp。
整数および浮動小数点値(JLS§4.2.2、JLS§4.2.4)に対するJavaプログラミング言語演算子のセマンティクスは、Java Virtual Machine命令セットのセマンティクスによって直接サポートされます。
Java Virtual Machineでは、整数データ型に対する操作中のオーバーフローは示されません。 例外をスローできる整数演算は、整数除算命令(idivおよびldiv)と整数余剰命令(iremおよびlrem)のみで、除数がゼロの場合はArithmeticExceptionをスローします。
Java Virtual Machineは、浮動小数点データ型に対する操作中にオーバーフローまたはアンダーフローを示しません。 つまり、浮動小数点命令によって、Java Virtual Machineが実行時例外をスローすることはありません(IEEE 754浮動小数点例外と混同しないでください)。 オーバーフローする演算は符号付き無限大を生成し、アンダーフローする演算は非正規値または符号付きゼロを生成し、数学的に一意の結果が定義されていない演算は NaNを生成します。 NaNをオペランドとするすべての数値演算は、結果として NaNを生成します。
long型(lcmp)の値を比較すると、符号付き比較が実行されます。
浮動小数点型の値(dcmpg、dcmpl、fcmpg、fcmpl)の比較は、IEEE 754の非シグナリング比較を使用して実行されます。
型変換命令では、Java Virtual Machineの数値型間の変換が可能です。 これらは、ユーザー・コードに明示的な変換を実装したり、Java Virtual Machineの命令セットに正統性の欠如を軽減するために使用できます。
Java Virtual Machineでは、次の拡張数値変換が直接サポートされます。
intからlong、floatまたはdouble
longからfloatまたはdouble
floatからdouble
拡大する数値変換命令は、i2l、i2f、i2d、l2f、l2dおよびf2dです。 これらのopコードのニーモニックは、型付き命令の命名規則および「to」を意味する2のパンニングの使用により、単純です。 たとえば、i2d命令は、int値をdoubleに変換します。
拡大する数値の変換では、数値の全体の大きさに関する情報は失われません。 実際、intからlongおよびintからdoubleへの変換では、情報がまったく失われず、数値は正確に保持されます。 floatからdoubleへの変換は、数値を正確に保持します。
intからfloatへの変換、またはlongからfloatへの変換、またはlongからdoubleへの変換では、精度が失われる可能性があります。つまり、値の最下位ビットの一部が失われる可能性があります。結果の浮動小数点値は、丸めから最も近い丸めポリシー(§2.8)を使用して、整数値の正しい丸めバージョンです。
精度が失われる可能性があるにもかかわらず、数値変換を拡張しても、Java Virtual Machineが実行時例外をスローすることはありません(IEEE 754浮動小数点例外と混同しないでください)。
intからlongへの数値の拡大変換では、int値の2つの補数表現に符号を付けて、より広い書式を塗りつぶします。 charの数値変換を整数型に拡大すると、char値の表現が拡張され、より広い書式になります。
数値変換の拡大は、整数型byte、charおよびshortからint型には存在しません。 §2.11.1に記載されているように、byte、charおよびshort型の値は内部的にint型に拡大され、これらの変換は暗黙的に行われます。
Java Virtual Machineでは、次の数値の絞込み変換も直接サポートされます。
intからbyte、shortまたはchar
longからint
floatからintまたはlong
doubleからint、longまたはfloat
数値変換命令は、i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、および d2fです。 数値変換を絞り込むと、符号が異なる値、大きさが異なる値、またはその両方になるため、精度が失われる可能性があります。
intまたはlongの数値を整数型Tに絞り込むと、nの最下位ビット以外のすべてのビットが破棄されます。ここで、nは、型Tを表すために使用されるビット数です。 これにより、結果の値が入力値と同じ符号を持たない場合があります。
浮動小数点値を整数型Tに絞り込む数値変換(Tはintまたはlong)では、浮動小数点値は次のように変換されます。
浮動小数点値がNaNの場合、変換の結果はintまたはlong 0になります。
それ以外の場合、浮動小数点値が無限大でない場合、浮動小数点値はゼロの丸めポリシー(§2.8)への丸めを使用して整数値 Vに丸められます。 次に2つの例を示します。
Tがlongで、この整数値をlongとして表すことができる場合、結果はlong値Vになります。
Tがint型で、この整数値をintとして表すことができる場合、結果はint値Vになります。
そうでない場合は、次のようになります。
値が小さすぎる(大きい大きさまたは負の無限大の負の値)必要があり、結果はint型またはlong型の最小表現可能値です。
または、値が大きすぎる(大きい大きさまたは正の無限大の正の値)必要があり、その結果はintまたはlong型の表現可能な最大値になります。
doubleからfloatへの数値の縮小変換は、IEEE 754に従って動作します。 結果は、最も近い丸めポリシー(§2.8)を使用して正しく丸められます。 floatとして表すには小さすぎる値は、float型の正または負のゼロに変換されます。floatとして表すには大きすぎる値は、正または負の無限大に変換されます。 double NaNは、常にfloat NaNに変換されます。
オーバーフロー、アンダーフロー、または精度の損失が発生する可能性があるにもかかわらず、数値型間の変換を絞り込むと、Java Virtual Machineはランタイム例外をスローしません(IEEE 754浮動小数点例外と混同しないでください)。
クラス・インスタンスと配列はどちらもオブジェクトですが、Java Virtual Machineでは、異なる命令セットを使用してクラス・インスタンスと配列を作成および操作します。
新しいクラス・インスタンスnewを作成します。
新しい配列newarray、anewarray、multianewarrayを作成します。
getstatic、putstatic、getfield、putfieldなどのクラスのフィールド(クラス変数と呼ばれるstaticフィールド)およびクラス・インスタンスのフィールド(インスタンス変数と呼ばれる非staticフィールド)にアクセスします。
オペランド・スタック(baload、caload、saload、iaload、laload、faload、daload、aaload)に配列コンポーネントをロードします。
オペランド・スタックの値を配列コンポーネントとして格納します: bastore、castore、sastore、iastore、lastore、fastore、dastore、aastore。
配列の長さを取得します: arraylength。
クラス・インスタンスまたは配列のプロパティ(instanceof、checkcast)を確認します。
オペランド・スタックの直接操作には、pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2、swapの命令が多数用意されています。
制御転送命令は、条件付きまたは無条件に、制御転送命令に続く命令以外の命令でJava Virtual Machineの実行を続行します。 これらを次に示します。
条件付きブランチ: ifeq、ifne、iflt、ifle、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmple、if_icmpgt if_icmpge、if_acmpeq、if_acmpne。
複合条件付きブランチ: tableswitch、lookupswitch。
無条件ブランチ: goto、goto_w、jsr、jsr_w、ret。
Java Virtual Machineには、int型およびreference型のデータと比較して条件付き分岐する一連の異なる命令があります。 また、null参照をテストする個別の条件付き分岐命令があるため、nullの具体的な値を指定する必要はありません(§2.4)。
boolean、byte、charおよびshort型のデータの比較に関する条件分岐は、int比較命令(§2.11.1)を使用して実行されます。 long、floatまたはdouble型のデータの比較に関する条件分岐は、データを比較し、比較のint結果を生成する命令(§2.11.3)を使用して開始されます。 後続のint比較命令は、この結果をテストし、条件付きブランチに影響を与えます。 intの比較に重点を置いているため、Java Virtual Machineでは、int型の条件分岐命令が豊富に補完されています。
すべてのint条件付き制御転送命令は、署名付き比較を実行します。
次の5つの手順でメソッドを呼び出します。
invokevirtualは、オブジェクトのインスタンス・メソッドを起動し、オブジェクトの(仮想)タイプにディスパッチします。
invokeinterfaceは、インタフェース・メソッドを呼び出して、特定のランタイム・オブジェクトによって実装されたメソッドを検索し、適切なメソッドを見つけます。
invokespecialは、インスタンス初期化メソッド(§2.9.1)または現在のクラスのメソッドまたはそのスーパータイプのいずれか、特別な処理を必要とするインスタンス・メソッドを呼び出します。
invokestaticは、指定されたクラス内のクラス(static)メソッドを呼び出します。
invokedynamicは、invokedynamic命令にバインドされたコール・サイト・オブジェクトのターゲットであるメソッドを呼び出します。 コール・サイト・オブジェクトは、命令の最初の実行前にブートストラップ・メソッドを実行した結果、Java Virtual Machineによってinvokedynamic命令の特定の字句発生にバインドされました。 したがって、invokedynamic命令が出現するたびに、メソッドを起動する他の命令とは異なり、一意のリンケージ状態があります。
戻り型で区別されるメソッド戻り命令は、ireturn (戻り型boolean、byte、char、shortおよびintに使用)、lreturn、freturn、dreturnおよびareturnです。 また、return命令は、void、インスタンス初期化メソッドおよびクラスまたはインタフェース初期化メソッドとして宣言されたメソッドから戻すために使用されます。
例外は、athrow命令を使用してプログラム的にスローされます。 例外は、異常な状態を検出した場合に、様々なJava Virtual Machine命令によってスローすることもできます。
Java Virtual Machineでは、単一の同期構成(モニター)によって、メソッド内のメソッドと一連の命令の同期がサポートされます。
メソッド・レベルの同期は、メソッドの呼出しおよび戻り値の一部として暗黙的に実行されます(§2.11.8)。 synchronizedメソッドは、実行時定数プールのmethod_info構造体(§4.6)で、メソッド呼出し命令によってチェックされるACC_SYNCHRONIZEDフラグによって区別されます。 ACC_SYNCHRONIZEDが設定されているメソッドを呼び出すと、実行スレッドはモニターに入り、メソッド自体を呼び出して、メソッド呼出しが正常に完了するか突然完了するかをモニターを終了します。 実行中のスレッドがモニターを所有している間は、ほかのスレッドがモニターに入ることはできません。 synchronizedメソッドの起動中に例外がスローされ、synchronizedメソッドが例外を処理しない場合、そのメソッドのモニターは、例外がsynchronizedメソッドから再スローされる前に自動的に終了します。
一連の命令の同期は、通常、Javaプログラミング言語のsynchronizedブロックをエンコードするために使用されます。 Java Virtual Machineには、このような言語構成をサポートするためのmonitorenterおよびmonitorexit命令が用意されています。 synchronizedブロックを適切に実装するには、Java Virtual Machine (§3.14)をターゲットとするコンパイラからの協力が必要です。
構造化ロックは、メソッドの起動時に、特定のモニターのすべての終了がそのモニターの前のエントリと一致する状況です。 Java Virtual Machineに送信されたすべてのコードが構造化ロックを実行する保証がないため、Java Virtual Machineの実装は許可されますが、構造化ロックを保証する次の2つのルールの両方を適用する必要はありません。 Tをスレッドとし、Mをモニターにします。 次に、
メソッドの起動中にTがMで実行するモニター・エントリの数は、メソッドの起動中にTがMで実行するモニター・イグジットの数と同じである必要があります。
メソッド呼出し中、MでTによって実行されたモニター・イグジットの数は、メソッド呼出し以降にMでTによって実行されたモニター・エントリの数を超えてしまうため、どの時点でもメソッド呼出し中はモニター・イグジットの数はできません。
synchronizedメソッドの呼出し時にJava Virtual Machineによって自動的に実行されるモニター・エントリおよび終了は、呼出しメソッドの呼出し中に発生するとみなされます。
Java Virtual Machineは、Java SEプラットフォームのクラス・ライブラリの実装に十分なサポートを提供する必要があります。 これらのライブラリの一部のクラスは、Java Virtual Machineの連携がないと実装できません。
Java Virtual Machineの特別なサポートが必要なクラスには、次のものが含まれます。
リフレクション(パッケージjava.lang.reflectのクラスやクラスClassなど)。
クラスまたはインタフェースのロードおよび作成。 最も明らかな例は、クラスClassLoaderです。
クラスまたはインタフェースのリンクと初期化。 前述のクラスの例は、このカテゴリにも該当します。
モジュール・システム(クラスModuleLayerなど)。
マルチスレッド(クラスThreadなど)。
弱い参照(パッケージjava.lang.refのクラスなど)。
上記のリストは、包括的なものではなく、説明的なものであることを意図しています。 これらのクラスまたはクラスが提供する機能の包括的なリストは、この仕様の範囲外です。 詳細は、Java SE Platformクラス・ライブラリの仕様を参照してください。
これまでのところ、この仕様では、Java Virtual Machineのパブリック・ビュー(classファイル形式と命令セット)がスケッチされています。 これらのコンポーネントは、ハードウェア、オペレーティング・システム、およびJava Virtual Machineの実装の独立性にとって不可欠です。 実装者は、Java SEプラットフォームを実装する各ホスト間でプログラムの断片を安全に通信する手段として、正確に追跡するブループリントとしてではなく、これらを考えることを好む場合があります。
パブリック・デザインとプライベート実装の境界線がどこにあるかを理解することが重要です。 Java Virtual Machineの実装では、classファイルを読み取ることができ、そこでJava Virtual Machineコードのセマンティクスを正確に実装する必要があります。 これを行う方法の1つは、このドキュメントを仕様として取得し、その仕様を文字通り実装することです。 ただし、実装者がこの仕様の制約内で実装を変更または最適化することは、完全に実現可能であり、望ましいことです。 classファイル形式が読み取られ、そのコードのセマンティクスが維持されるかぎり、実装者はこれらのセマンティクスをどのような方法でも実装できます。 正しい外部インタフェースが慎重に保守されているかぎり、実装者のビジネスは「内部」です。
例外にはいくつかあります。デバッガ、プロファイラおよびジャストインタイム・コード・ジェネレータはそれぞれ、通常「内部」とみなされるJava Virtual Machineの要素へのアクセスを要求できます。 適宜、Oracleは、他のJava Virtual Machineインプリメンタおよびツール・ベンダーと連携して、このようなツールで使用するためのJava Virtual Machineへの共通インタフェースを開発し、それらのインタフェースを業界全体で促進します。
実装者は、この柔軟性を使用して、Java Virtual Machineの実装をカスタマイズし、高パフォーマンス、低メモリー使用、または移植性を実現できます。 特定の実装で意味があるのは、その実装の目標によって異なります。 実装オプションの範囲は次のとおりです。
ロード時または実行時にJava Virtual Machineコードを別の仮想マシンの命令セットに変換します。
ロード時または実行時にJava Virtual MachineコードをホストCPUのネイティブ命令セット(ジャストインタイム(JIT)に変換する。
正確に定義された仮想マシンとオブジェクトファイル形式が存在しても、実装者の創造性を大幅に制限する必要はありません。 Java Virtual Machineは、様々な実装をサポートするように設計されており、実装間の互換性を維持しながら、新しく興味深いソリューションを提供します。