目次
Java Virtual Machineは、クラスおよびインタフェースを動的にロード、リンクおよび初期化します。 ロードとは、特定の名前を持つクラスまたはインタフェース型のバイナリ表現を検索し、そのバイナリ表現からクラスまたはインタフェースを作成するプロセスです。 リンクとは、クラスまたはインタフェースを取得し、それをJava Virtual Machineのランタイム状態に組み合せて実行できるようにするプロセスです。 クラスまたはインタフェースの初期化は、クラスまたはインタフェース初期化メソッド<clinit> (§2.9.2)の実行で構成されます。
この章では、§5.1で、Java Virtual Machineがクラスまたはインタフェースのバイナリ表現からシンボリック参照を導出する方法について説明します。§5.2では、ロード、リンクおよび初期化のプロセスが最初にJava Virtual Machineによってどのように開始されるかについて説明します。§5.3では、クラスおよびインタフェースのバイナリ表現がクラス・ローダーによってどのようにロードされるか、およびクラスとインタフェースがどのように作成されるかを指定します。 リンクについては、§5.4を参照してください。§5.5では、クラスとインタフェースがどのように初期化されたかについて詳しく説明しています。§5.6では、ネイティブメソッドの結合の概念を紹介しています。 最後に、§5.7では、Java Virtual Machineが終了するタイミングについて説明します。
Java Virtual Machineは、クラスおよびインタフェースごとにランタイム定数プールを保持します(§2.5.5)。 このデータ構造は、従来のプログラミング言語実装のシンボルテーブルの多くの目的を果たします。 クラスまたはインタフェースのバイナリ表現のconstant_pool表(§4.4)は、クラスまたはインタフェースの作成時にランタイム定数プールを構築するために使用されます(§5.3)。
実行時定数プールには、後で解決される可能性があるシンボリック参照(§5.4.3)と、それ以上の処理を必要としない静的定数の2種類のエントリがあります。
ランタイム定数プール内のシンボリック参照は、各エントリの構造に従って、constant_pool表のエントリから導出されます。
クラスまたはインタフェースへのシンボリック参照は、CONSTANT_Class_info構造体(§4.4.1)から導出されます。 このような参照は、次の形式でクラスまたはインタフェースの名前を示します。
この章がクラスまたはインタフェースの名前を参照する場合、その名前は前述の形式であると理解する必要があります。 (これは、Class.getNameメソッドによって返される形式でもあります。)
クラスまたはインタフェースのフィールドへのシンボリック参照は、CONSTANT_Fieldref_info構造体(§4.4.2)から導出されます。 このような参照は、フィールドの名前と記述子、およびフィールドが見つかるクラスまたはインタフェースへのシンボリック参照を提供します。
クラスのメソッドへのシンボリック参照は、CONSTANT_Methodref_info構造体(§4.4.2)から導出されます。 このような参照は、メソッドの名前と記述子、およびメソッドが見つかるクラスへのシンボリック参照を提供します。
インタフェースのメソッドへのシンボリック参照は、CONSTANT_InterfaceMethodref_info構造体(§4.4.2)から導出されます。 このような参照は、インタフェース・メソッドの名前と記述子、およびメソッドが見つかるインタフェースへのシンボリック参照を提供します。
メソッド・ハンドルへのシンボリック参照は、CONSTANT_MethodHandle_info構造体(§4.4.8)から導出されます。 このような参照は、メソッド・ハンドルの種類に応じて、クラス、インタフェースのフィールド、クラスのメソッド、またはインタフェースのメソッドへのシンボリック参照を提供します。
メソッド型へのシンボリック参照は、CONSTANT_MethodType_info構造体(§4.4.9)から導出されます。 このような参照はメソッド記述子(§4.3.3)を与えます。
動的計算定数へのシンボリック参照は、CONSTANT_Dynamic_info構造体(§4.4.10)から導出されます。 このような参照では、次のものが提供されます。
定数の値を計算するために呼び出されるメソッド・ハンドルへのシンボリック参照。
メソッド・ハンドルが呼び出されたときに静的引数として機能する、一連のシンボリック参照と静的定数。
修飾されていない名前とフィールド記述子。
動的に計算されるコール・サイトへのシンボリック参照は、CONSTANT_InvokeDynamic_info構造体(§4.4.10)から導出されます。 このような参照では、次のものが提供されます。
invokedynamic命令(§invokedynamic)の過程で呼び出され、java.lang.invoke.CallSiteのインスタンスを計算するメソッド・ハンドルへのシンボリック参照。
メソッド・ハンドルが呼び出されたときに静的引数として機能する、一連のシンボリック参照と静的定数。
修飾されていない名前とメソッド記述子。
ランタイム定数プールの静的定数は、各エントリの構造に従って、constant_pool表のエントリからも導出されます。
文字列定数は、Stringクラスのインスタンスに対するreferenceで、CONSTANT_String_info構造体(§4.4.3)から導出されます。 文字列定数を導出するために、Java Virtual Machineは、CONSTANT_String_info構造によって指定された一連のコード・ポイントを調べます。
Unicodeコードのシーケンスを含むクラスStringのインスタンスでメソッドString.internが以前に呼び出された場合、文字列定数はクラスStringの同じインスタンスに対するreferenceになります。
それ以外の場合は、CONSTANT_String_info構造によって指定されたUnicodeコード・ポイントのシーケンスを含むクラスStringの新しいインスタンスが作成されます。 文字列定数は、新しいインスタンスに対するreferenceです。 最後に、メソッドString.internが新しいインスタンスで呼び出されます。
数値定数は、CONSTANT_Integer_info、CONSTANT_Float_info、CONSTANT_Long_infoおよびCONSTANT_Double_info構造体(§4.4.4、§4.4.5)から導出されます。
CONSTANT_Float_info構造はIEEE 754単一形式の値を表し、CONSTANT_Double_info構造はIEEE 754二重形式の値を表します。 したがって、これらの構造体から導出される数値定数は、それぞれIEEE 754単精度形式と倍精度形式を使用して表すことができる値である必要があります。
constant_pool表の残りの構造(説明構造CONSTANT_NameAndType_info、CONSTANT_Module_infoおよびCONSTANT_Package_info、および基礎構造CONSTANT_Utf8_info)は、ランタイム定数プールを構築する場合にのみ間接的に使用されます。 実行時定数プール内のエントリは、これらの構造に直接対応していません。
ランタイム定数プールの一部のエントリはロード可能であり、次のことを意味します。
ランタイム定数プールのエントリは、ロード可能なconstant_pool表のエントリから導出された場合にロード可能です(表4.4-Cを参照)。 したがって、実行時定数プール内の次のエントリはロード可能です。
クラスおよびインタフェースへのシンボリック参照
メソッド・ハンドルへのシンボリック参照
メソッド型へのシンボリック参照
動的に計算される定数へのシンボリック参照
静的定数
Java Virtual Machineは、ブートストラップ・クラス・ローダー(§5.3.1)またはユーザー定義クラス・ローダー(§5.3.2)を使用して初期クラスまたはインタフェースを作成することで起動します。 次に、Java Virtual Machineは、初期クラスまたはインタフェースをリンクして初期化し、そのインスタンス(必要な場合)を作成し、(JLS§12.1.4)で指定されたmainメソッドを呼び出します。 このメソッドを呼び出すと、それ以上の実行がすべて行われます。 インスタンス初期化メソッドのJava Virtual Machine命令(必要な場合)および初期クラスまたはインタフェースのmainメソッドを実行すると、追加のクラスおよびインタフェースのリンク(およびそれ以降の作成)および追加のメソッドの呼出しが発生する可能性があります。
初期クラスまたはインタフェースは、実装に依存する方法で指定されます。 たとえば、初期クラスまたはインタフェースはコマンドライン引数として指定できます。 あるいは、Java仮想マシンの実装自体が、クラス・ローダーを設定し、その後にアプリケーションをロードする初期クラスを提供することもあります。 初期クラスまたはインタフェースのその他の選択は、前の段落で示した指定と矛盾しないかぎり可能です。
対照的に、初期クラスまたはインタフェースのmainメソッドの選択および呼出しは、(JLS§12.1.4)に示されている実装に依存しないルールに従って進みます。
Nという名前で示されるクラスまたはインタフェースCの作成は、Java Virtual Machineのメソッド領域におけるCの実装固有の内部表現の構築(§2.5.4)で構成されます。
クラスまたはインタフェースの作成は、別のクラスまたはインタフェースDによってトリガーされます。ランタイム定数プールは、N (§5.4.3.1)という名前でCを象徴的に参照します。 Nが配列クラスを示さない場合、Java Virtual Machineはクラス・ローダーを使用して、N (§4.1)というクラスまたはインタフェースのバイナリ表現を見つけます。 クラス・ローダーは、バイナリ表現を見つけた後、Java Virtual Machineをオンにして、バイナリ表現からクラスまたはインタフェースCを導出してから、メソッド領域にCを作成します。 配列クラスは外部バイナリ表現を持たず、別のプロセスを介してJava Virtual Machineによって作成されます。
クラスまたはインタフェースの作成は、リフレクションなどの特定のJava SE Platformクラス・ライブラリ(§2.12)でメソッドを呼び出すDによってトリガーされることもあります。
クラス・ローダーには2種類あります: Java Virtual Machineによって提供されるブートストラップ・クラス・ローダーおよびユーザー定義のクラス・ローダー。 すべてのユーザー定義クラス・ローダーは、抽象クラスClassLoaderのサブクラスのインスタンスです。 アプリケーションでは、Java Virtual Machineがクラスを動的に作成する方法を拡張するために、ユーザー定義のクラス・ローダーを採用しています。 ユーザー定義のクラス・ローダーは、ユーザー定義のソースが作成元であるクラスを作成するために使用できます。 たとえば、クラスは、ネットワーク全体にわたってダウンロードしたり、その場で生成したり、暗号化されたファイルから抽出できます。
Java Virtual Machineがクラス・ローダーLにNというクラスまたはインタフェースのバイナリ表現を検索するように要求すると、LはNで示されるクラスまたはインタフェースCをロードします。 Lは、バイナリ表現を探し、Java Virtual Machineにバイナリ表現からCを導出および作成するように依頼することで、Cを直接ロードできます。 または、Lは、Cを直接または間接的にロードする別のクラス・ローダーに委任することで、Cを間接的にロードできます。
LがCを直接ロードする場合、LdefinesC、またはLがdefining loaderのCであるとします。
LがCを直接ロードするか間接ロードするかに関係なく、LはCのロードを開始するか、またはLはCのローダーの開始であるとします。
クラス・ローダーの委任により、Java Virtual Machineのリクエストでロードを開始するローダーL1が、クラスまたはインタフェースを定義してロードを完了するローダーL2と同じでない場合があります。 この場合、L1とL2のそれぞれがCのロードを開始するか、L1とL2のそれぞれがCのローダーの開始であるとします。 L1とL2の間の委任チェーン内のローダーは、Cの開始ローダーとはみなされません。
Cや Dなどの識別子を使用するかわりに、次の表記法を使用してクラスまたはインタフェースを表すことがあります。
<N、Ld> - Nはクラスまたはインタフェースの名前を示し、Ldはクラスまたはインタフェースの定義ローダーを示します。
NLi - ここで、Nはクラスまたはインタフェースの名前を示し、Liはクラスまたはインタフェースの開始ローダーを示します。
クラスまたはインタフェースのロードが、Java Virtual Machineとクラス・ローダー(または委任が発生した場合は複数のクラス・ローダー)の共同作業であることは明らかです。 ロードの最終的な結果は、Java Virtual Machineがそのメソッド領域にクラスまたはインタフェースを作成するため、多くの場合、クラスまたはインタフェースがロードされ、それによって作成されると記述すると便利です。
ロードの複雑なバック・アンド・フォースの性質は、ユーザー定義のクラス・ローダーが任意の動作を示す機能と組み合せることで、Java Virtual Machineがクラスまたはインタフェースを作成した後に例外をスローできますが、ロードに参加するすべてのクラス・ローダーが完了する前にスローできます。 この仕様では、クラスまたはインタフェースのロードおよび作成プロセスと呼ばれることが多い場合に、このような例外が発生します。
Java Virtual Machineでは、3つのプロシージャのいずれかを使用して、クラスまたはインタフェースDの実行時定数プールにNという名前で示されるクラスまたはインタフェースCを作成します。
Nが非配列クラスまたはインタフェースを示しており、Dがブートストラップ・クラス・ローダーによって定義されている場合、ブートストラップ・クラス・ローダーはCのロードを開始します(§5.3.1)。
Nが非配列クラスまたはインタフェースを示しており、Dがユーザー定義クラス・ローダーによって定義されている場合、同じユーザー定義クラス・ローダーがCのロードを開始します(§5.3.2)。
Nが配列クラスを示す場合、Java Virtual Machineは、Dの定義ローダー(§5.3.3)に関連して、Nで示される配列クラスCを作成します。
Dの定義ローダーは、配列クラスを作成する過程で関連しますが、このローダーは、配列クラスのロードおよび作成には使用されません。
クラス・ローダーがバイナリ表現を見つけているとき、またはJava Virtual Machineがそこからクラスを導出および作成しているときに、クラスまたはインタフェースのロード中にエラーが発生した場合、エラーは、ロードされるクラスまたはインタフェースを(直接的または間接的に)使用するプログラム内のポイントでスローされる必要があります。
適切に動作するクラス・ローダーは、次の3つのプロパティを保持する必要があります:
同じ名前を指定すると、適切なクラス・ローダーは常に同じClassオブジェクトを返す必要があります。
クラス・ローダーL1がクラスCのロードを別のローダーL2に委任した場合、Cのダイレクト・スーパークラスまたはダイレクト・スーパーインタフェースまたはダイレクト・スーパーインタフェースとして発生する任意のタイプTに対して、またはCのフィールド、またはCのメソッドまたはコンストラクタの仮パラメータの型として、またはC、L1およびL2のメソッドの戻り型として、同じClassオブジェクトを返す必要があります。
ユーザー定義のクラス・ローダーがクラスとインタフェースのバイナリ表現をプリフェッチするか、関連するクラスのグループをまとめてロードする場合、プリフェッチやグループ・ロードを行わずに発生した可能性のあるプログラム内のポイントでのみロード・エラーを反映する必要があります。
作成後、クラスまたはインタフェースは名前のみでなく、バイナリ名(§4.2.1)と定義ローダーのペアによって決定されます。 このような各クラスまたはインタフェースは、単一のランタイム・パッケージに属します。 クラスまたはインタフェースの実行時パッケージは、パッケージ名とクラスまたはインタフェースの定義ローダーによって決定されます。
ブートストラップ・クラス・ローダーを使用して、Nで示される非配列クラスまたはインタフェースCをロードおよび作成するプロセスは、次のとおりです。
まず、Java Virtual Machineは、ブートストラップ・クラス・ローダーがNで示されるクラスまたはインタフェースの開始ローダーとしてすでに記録されているかどうかを判断します。 その場合、このクラスまたはインタフェースは Cであり、クラスのロードまたは作成は必要ありません。
それ以外の場合、Java Virtual Machineは引数Nをブートストラップ・クラス・ローダー上のメソッドの呼出しに渡します。 Cをロードするために、ブートストラップ・クラス・ローダーは、プラットフォームに依存した方法でCのパージされた表現を探し、Java Virtual Machineに、ブートストラップ・クラス・ローダーを使用してNで示されたクラスまたはインタフェースCを、§5.3.5のアルゴリズムを使用して、Cを作成するように依頼します。
通常、クラスまたはインタフェースは階層ファイル・システムのファイルを使用して表され、クラスまたはインタフェースの名前はファイルのパス名でエンコードされ、その特定に役立ちます。
Cのパージされた表現が見つからない場合、ブートストラップ・クラス・ローダーはClassNotFoundExceptionをスローします。 Cをロードおよび作成するプロセスは、ClassNotFoundExceptionの原因であるNoClassDefFoundErrorで失敗します。
Cのパージされた表現が見つかったが、パージされた表現から Cを導出できない場合は、同じ理由で Cをロードおよび作成するプロセスが失敗します。
それ以外の場合は、Cをロードおよび作成するプロセスが成功します。
ユーザー定義クラス・ローダーLを使用して、Nで示される非配列クラスまたはインタフェースCをロードおよび作成するプロセスは、次のとおりです。
まず、Java Virtual Machineは、LがすでにNで示されるクラスまたはインタフェースの開始ローダーとして記録されているかどうかを判断します。 その場合、このクラスまたはインタフェースは Cであり、クラスのロードまたは作成は必要ありません。
それ以外の場合、Java Virtual MachineはクラスClassLoaderのloadClassメソッドをLで起動し、クラスまたはインタフェースのNという名前を渡します。 Lは、ロードする次の2つの操作のいずれかを実行し、それによってクラスまたはインタフェースCを作成する必要があります。
クラス・ローダーLは、Cを直接ロードできます。 これを行うには、CをClassFile構造体(§4.1)として表すためにパージするバイトの配列を取得してから、クラスClassLoaderのメソッドdefineClassを呼び出します。 defineClassを起動すると、Java Virtual Machineは、Lを使用してバイト配列からNで示されるクラスまたはインタフェースCを導出し、Cを§5.3.5のアルゴリズムを使用して作成します。 Lは、loadClassの結果としてdefineClassの結果を使用する必要があります。
クラス・ローダーLは、Cのロードを他のクラス・ローダーLに委任することで、Cを間接的にロードできます。 これを行うには、引数NをLのメソッドの呼出し(通常はクラスClassLoaderのloadClassメソッド)に渡します。 Lは、そのメソッドの結果をloadClassの結果として使用する必要があります。
実行される操作に関係なく、次のルールが適用されます。
クラス・ローダーが、Nで示されるクラスまたはインタフェースの、インポートされた表現を検出できない場合は、ClassNotFoundExceptionをスローする必要があります。 Cをロードおよび作成するプロセスは、ClassNotFoundExceptionの原因であるNoClassDefFoundErrorで失敗します。
クラス・ローダーがCのパージされた表現を検出したが、パージされた表現からのCの導出に失敗した場合、Cのロードおよび作成のプロセスは同じ理由で失敗します。
クラス・ローダーがClassNotFoundException以外の例外をスローした場合、Cのロードおよび作成のプロセスは同じ理由で失敗します。
LでのloadClassの起動に結果がある場合、次のようになります。
結果がnullの場合、または結果がN以外の名前を持つクラスまたはインタフェースの場合、結果は破棄され、ロードおよび作成のプロセスがNoClassDefFoundErrorで失敗します。
それ以外の場合は、作成されたクラスまたはインタフェース Cが結果になります。 Java Virtual Machineは、LがCの開始ローダー(§5.3.4)であることを記録します。 Cのロードおよび作成プロセスは成功します。
JDK 1.1以降、OracleのJava Virtual Machine実装は、クラス・ローダーで1引数のloadClassメソッドを呼び出して、クラスまたはインタフェースをロードするようにしました。 loadClassの引数は、ロードされるクラスまたはインタフェースの名前です。 loadClassメソッドには2つの引数のバージョンもあります。2番目の引数は、クラスまたはインタフェースをリンクするかどうかを示すbooleanです。 JDK 1.0.2では2引数バージョンのみが提供され、OracleのJava Virtual Machine実装はロードされたクラスまたはインタフェースをリンクするためにそれに依存していました。 JDK 1.1以降、OracleのJava Virtual Machine実装は、クラス・ローダーに依存せずに、クラスまたはインタフェースを直接リンクします。
次のステップを使用して、クラス・ローダーLに関連してNという名前の配列クラスCを作成します。 Lは、ブートストラップ・クラス・ローダーまたはユーザー定義クラス・ローダーのいずれかです。
まず、Java Virtual Machineは、LがNと同じコンポーネント・タイプの配列クラスの開始ローダーとしてすでに記録されているかどうかを判断します。 その場合、このクラスは Cであり、配列クラスを作成する必要はありません。
それ以外の場合は、次のステップを実行してCを作成します。
コンポーネント・タイプがreference型の場合、この項のアルゴリズム(§5.3)は、Lを使用して再帰的に適用され、Cのコンポーネント・タイプがロードされて作成されます。
Java Virtual Machineは、指定されたコンポーネント・タイプとディメンション数を使用して新しい配列クラスを作成します。
コンポーネント・タイプがreference型の場合、Java Virtual Machineは、コンポーネント・タイプの定義ローダーを定義ローダーとしてCとマークします。 それ以外の場合、Java Virtual Machineは、ブートストラップ・クラス・ローダーを定義ローダーとしてCとマークします。
いずれの場合も、Java Virtual Machineは、LがCの開始ローダー(§5.3.4)であることを記録します。
コンポーネント・タイプがreference型の場合、配列クラスのアクセシビリティは、そのコンポーネント・タイプ(§5.4.4)のアクセシビリティによって決まります。 それ以外の場合、配列クラスはすべてのクラスおよびインタフェースからアクセスできます。
クラス・ローダーが存在する場合にタイプ・セーフ・リンケージを確保するには、特別な注意が必要です。 2つの異なるクラス・ローダーがNで示されるクラスまたはインタフェースのロードを開始すると、Nという名前が各ローダーで異なるクラスまたはインタフェースを示す場合があります。
クラスまたはインタフェースC = <N1の場合、L1>は、別のクラスまたはインタフェースD = <N2、L2>のフィールドまたはメソッドへのシンボリック参照を作成します。シンボリック参照には、フィールドの型を指定する記述子、またはメソッドの戻り型と引数型が含まれます。 フィールドまたはメソッド記述子(§4.3.2、§4.3.3)に記載されているクラス名またはインタフェース名Nは、L1によってロードされ、L2によってロードされるときに、同じクラスまたはインタフェースを示すことが不可欠です。
これを確実にするために、Java Virtual Machineでは、準備(§5.4.2)および解決(§5.4.3)中に、NL1 = NL2という形式のロード制約が課されます。 これらの制約を適用するために、Java Virtual Machineは特定の規定時間(§5.3.1、§5.3.2、§5.3.3および§5.3.5を参照)に、特定のローダーが特定のクラスの開始ローダーであることを記録します。 ローダーがクラスの開始ローダーであることを記録した後、Java Virtual Machineはすぐにロード制約に違反しているかどうかを確認する必要があります。 その場合、レコードは撤回され、Java Virtual MachineはLinkageErrorをスローし、記録の実行の原因となったロード操作は失敗します。
同様に、ロード制約を課した後(§5.4.2、§5.4.3.2、§5.4.3.3および§5.4.3.4を参照)、Java Virtual Machineは、ロード制約に違反しているかどうかをすぐに確認する必要があります。 その場合、新たに課せられたロード制約が撤回され、Java Virtual MachineはLinkageErrorをスローし、制約が課せられる原因となった操作(場合によっては解決または準備)は失敗します。
ここで説明する状況は、Java Virtual Machineがロード制約に違反しているかどうかをチェックする唯一の時間です。 ロード制約は、次の4つの条件がすべて保持されている場合にのみ違反します。
LがJava Virtual MachineによってNという名前のクラスCの開始ローダーとして記録されるように、ローダーLが存在します。
LがJava Virtual MachineによってNという名前のクラスCの開始ローダーとして記録されるように、ローダーLが存在します。
強制制約のセット(反射的、推移的クローズ)によって定義された等価関係は、NL = NL'を意味します。
C ≠ C '。
クラス・ローダーと型安全性に関する完全な説明は、この仕様の範囲外です。 より包括的な説明については、Sheng LiangとGilad BrachaによるDynamic Class Loading in the Java Virtual Machineを参照してください(Proceedings of the 1998 ACM SIGPLAN Conference on Object-Oriented Programming Systems、 Languages and Applications)。
classファイル表現からのクラスの導出
クラス・ローダーでは、ローダーによって提供されるバイナリ表現(§5.3.1、§5.3.2)からクラスまたはインタフェースを導出および作成するために、Java Virtual Machineの連携が必要です。 次のステップは、Nで示されるクラスまたはインタフェースCを、クラス・ローダーLによってリクエストされた場合に、classファイル形式のパージされた表現から導出するために使用されます。
まず、Java Virtual Machineは、Nで示されるクラスまたはインタフェースを導出するためのクラス・ローダーLによるリクエストが許可されるかどうかを判断します。
LがすでにNで示されるクラスまたはインタフェースの開始ローダーとして記録されている場合、導出はLinkageErrorをスローします。
Java Virtual Machineがすでに、クラス・ローダーLによってリクエストされたNによって示されたクラスまたはインタフェースを導出する処理中である場合、導出はClassCircularityErrorをスローします。
次に、Java Virtual Machineは、インポートされた表現の解析を試みます。 インポートされた表現は、実際には Cの有効な表現ではない可能性があるため、派生は次の問題を検出する必要があります。
インポートされた表現がClassFile構造体でない場合(§4.1、§4.8)、導出はClassFormatErrorをスローします。
それ以外の場合、インポートされた表現がサポートされているメジャー・バージョンまたはマイナー・バージョン(§4.1)でない場合は、導出によってUnsupportedClassVersionErrorがスローされます。
ClassFormatErrorのサブクラスであるUnsupportedClassVersionErrorは、JDK 1.2で導入され、サポートされていないバージョンのclassファイル形式を使用する表現を持つクラスをロードしようとしたために、ClassFormatErrorを簡単に識別できるようにしました。 JDK 1.1以前では、クラスがシステム・クラス・ローダーまたはユーザー定義クラス・ローダーによってロードされているかどうかに応じて、サポートされていないバージョンの場合にNoClassDefFoundErrorまたはClassFormatErrorのインスタンスがスローされました。
それ以外の場合、インポートされた表現が実際にNという名前のクラスまたはインタフェースを表していないと、導出によってNoClassDefFoundErrorがスローされます。
これは、パージされた表現に、N以外の名前を指定するthis_classアイテムまたはACC_MODULEフラグが設定されたaccess_flagsアイテムがある場合に発生します。
Cにダイレクト・スーパークラスがある場合、Cからそのダイレクト・スーパークラスへのシンボリック参照は、§5.4.3.1のアルゴリズムを使用して解決され、LはCの定義ローダーとして機能します。 Cがインタフェースの場合は、直接スーパークラスとしてObjectが必要であり、これはすでにロードされている必要があります。 直接スーパークラスを持たないのはObjectのみです。
クラスまたはインタフェース解決の失敗の結果としてスローできる例外は、導出の結果としてスローできます。 また、導出では次の問題を検出する必要があります。
Cの直接スーパークラスとして指定されたクラスまたはインタフェースが、実際にはインタフェースまたはfinalクラスである場合、導出はIncompatibleClassChangeErrorをスローします。
それ以外の場合、Cの直接スーパークラスとして指定されたクラスにPermittedSubclasses属性(§4.7.31)があり、次のいずれかに該当する場合は、導出によってIncompatibleClassChangeErrorがスローされます。
それ以外の場合、Cがクラスで、Cで宣言された一部のインスタンス・メソッドがCのスーパークラスで宣言されたfinalインスタンス・メソッドをオーバーライド(§5.4.5)できると、導出によってIncompatibleClassChangeErrorがスローされます。
Cに直接スーパーインタフェースがある場合、Cからその直接スーパーインタフェースへのシンボリック参照は、§5.4.3.1のアルゴリズムを使用して解決され、Lは Cの定義ローダーとして機能します。
クラスまたはインタフェース解決の失敗の結果としてスローできる例外は、導出の結果としてスローできます。 また、導出では次の問題を検出する必要があります。
Cの直接スーパーインタフェースとして指定されたクラスまたはインタフェースが実際にはインタフェースではない場合、導出はIncompatibleClassChangeErrorをスローします。
それ以外の場合、Cで指定された直接スーパーインタフェースごとに、スーパーインタフェースにPermittedSubclasses属性(§4.7.31)があり、次のいずれかに該当する場合は、導出によってIncompatibleClassChangeErrorがスローされます。
スーパーインタフェースは、Cとは異なるランタイム・モジュールにあります。
CにはACC_PUBLICフラグが設定されておらず(§4.1)、スーパーインタフェースはCとは異なるランタイム・パッケージにあります。
スーパーインタフェースのPermittedSubclasses属性のclasses配列内のエントリは、Nという名前のクラスまたはインタフェースを参照しません。
ステップ1から4で例外がスローされない場合、クラスまたはインタフェースCの導出は成功します。 Java Virtual Machineは、定義ローダーとしてLを持つようにCをマークし、LがCの開始ローダー(§5.3.4)であることを記録し、メソッド領域にCを作成します(§2.5.4)。
導出が成功すると、Cのロードおよび作成のプロセスは、Cのロードに関与するすべてのクラス・ローダーが(直接または間接的に)結果としてCを返すまで完了しません。 ユーザー定義クラス・ローダーの動作によっては、Cのロードおよび作成のプロセスがまだ失敗する場合があります(§5.3.2)。
ステップ1から4で例外がスローされた場合、クラスまたはインタフェースCの導出は、その例外で失敗します。
クラスまたはインタフェースを導出するリクエストは、複数のスレッドで実行されるクラス・ローダー・コードによって同時に行うことができますが、導出プロセスは順次行われます。 Java Virtual Machine実装では、特定のクラス・ローダーによる特定の名前のクラスまたはインタフェースを導出するリクエストが一度に1つのみ処理され、他のすべてのリクエストは最初のリクエストが完了するまで待機します。
導出プロセスで指定したとおり、最初のリクエストが成功した場合、後続のリクエストは許可されません。 ClassLoader APIは、導出リクエストを同期し、成功した結果をキャッシュするメカニズムを提供して、冗長な導出リクエストが発生しないようにします。
Java Virtual Machineは、モジュールへのクラスおよびインタフェースの編成をサポートしています。 モジュール M内のクラスまたはインタフェース Cのメンバーシップは、M (§5.4.4)以外のモジュールのクラスおよびインタフェースからの Cへのアクセスを制御するために使用されます。
モジュール・メンバーシップは、ランタイム・パッケージ(§5.3)で定義されています。 プログラムは、各モジュール内のパッケージの名前、および名前付きパッケージのクラスとインタフェースを作成するクラス・ローダーを決定します。その後、クラスModuleLayerのdefineModulesメソッドの呼出しに対するパッケージおよびクラス・ローダーを指定します。 defineModulesを起動すると、Java Virtual Machineは、クラス・ローダーのランタイム・パッケージに関連付けられた新しいランタイム・モジュールを作成します。
すべてのランタイム・モジュールは、エクスポートするランタイム・パッケージを示します。これは、それらのランタイム・パッケージ内のpublicクラスおよびインタフェースへのアクセスに影響します。 すべてのランタイム・モジュールは、読取りする他のランタイム・モジュールも示します。これは、それらのランタイム・モジュール内のpublic型およびインタフェースへの独自のコードによるアクセスに影響します。
クラスがランタイム・モジュール内にあるということは、クラスのランタイム・パッケージがそのランタイム・モジュールに関連付けられている(または、クラスが実際に作成されている場合は関連付けられている)場合です。
クラス・ローダーによって作成されるクラスは、1つのランタイム・パッケージ内に存在するため、1つのランタイム・モジュールのみに存在します。これは、Java Virtual Machineでは、複数のランタイム・モジュールに関連付けられている(つまり、複数のランタイム・モジュールにまたがって分割される)ランタイム・パッケージがサポートされていないためです。
ランタイム・モジュールは、defineModulesのセマンティクスによって、1つのクラス・ローダーに暗黙的にバインドされます。 一方、Java Virtual Machineでは、クラス・ローダーのすべてのランタイム・パッケージを同じランタイム・モジュールに関連付ける必要がないため、クラス・ローダーは複数のランタイム・モジュールにクラスを作成できます。
つまり、クラス・ローダーとランタイム・モジュール間の関係は1:1である必要はありません。 ロードする特定のモジュール・セットについて、各モジュール内のパッケージの名前がそのモジュール内でのみ見つかることをプログラムが判断できる場合、プログラムはdefineModulesの呼出しに対して1つのクラス・ローダーのみを指定できます。 このクラス・ローダーは、複数のランタイム・モジュールにまたがるクラスを作成します。
defineModulesによって作成されるすべてのランタイム・モジュールは、レイヤーの一部です。 レイヤーは、一連のランタイム・モジュールにクラスを作成するために共同で機能する一連のクラス・ローダーを表します。 レイヤーには、Java Virtual Machineによって提供されるブート・レイヤーとユーザー定義レイヤーの2種類があります。 ブート・レイヤーは、実装に依存した方法でJava Virtual Machineの起動時に作成されます。 標準ランタイム・モジュールjava.baseを、ブートストラップ・クラス・ローダーで定義された標準ランタイム・パッケージ(java.langなど)に関連付けます。 ユーザー定義レイヤーは、java.baseおよびその他の標準ランタイム・モジュールに依存するランタイム・モジュールのセットを構築するために、プログラムによって作成されます。
ランタイム・モジュールは、defineModulesのセマンティクスによって暗黙的に1つのレイヤーの一部になります。 ただし、クラス・ローダーでは、異なるレイヤーのランタイム・モジュールにクラスを作成できます。これは、同じクラス・ローダーをdefineModulesの複数の呼出しに指定できるためです。 アクセス制御は、クラスを作成したクラス・ローダーまたはクラス・ローダーが処理するレイヤーではなく、クラスのランタイム・モジュールによって制御されます。
レイヤーに指定されたクラス・ローダーのセットと、レイヤーの一部であるランタイム・モジュールのセットは、レイヤーの作成後に不変になります。 ただし、ModuleLayerクラスでは、プログラムがユーザー定義レイヤー内のランタイム・モジュール間の関係を動的に制御できます。
ユーザー定義レイヤーに複数のクラス・ローダーが含まれている場合、クラス・ローダー間の委任は、レイヤーを作成したプログラムの責任となります。 Java Virtual Machineでは、レイヤーのランタイム・モジュールが相互に読み取る方法に従って、レイヤーのクラス・ローダーが相互に委任されているかどうかはチェックされません。 さらに、レイヤーのランタイム・モジュールがModuleLayerクラスを介して変更され、追加のランタイム・モジュールが読み取られる場合、Java Virtual Machineは、レイヤーのクラス・ローダーが、対応する方法で委任するためにバンド外メカニズムによって変更されていることをチェックしません。
クラス・ローダーとレイヤーには類似点と相違点があります。 一方、レイヤーはクラス・ローダーに似ており、それぞれが、以前にモジュールまたはクラスを作成した1つ以上の親レイヤーまたはクラス・ローダーにそれぞれ委任できます。 つまり、レイヤーに指定されるモジュールのセットは、レイヤーに指定されていないモジュールに依存し、かわりに1つ以上の親レイヤーに以前に指定されているモジュールに依存する場合があります。 一方、レイヤーは、新しいモジュールの作成に1回のみ使用できますが、クラス・ローダーは、defineClassメソッドの複数の呼出しを介して、いつでも新しいクラスまたはインタフェースを作成するために使用できます。
クラス・ローダーは、クラス・ローダーが処理するレイヤーによってランタイム・モジュールに関連付けられていなかったランタイム・パッケージ内のクラスまたはインタフェースを定義できます。 これは、ランタイム・パッケージがdefineModulesに指定されていない名前付きパッケージを体現する場合、またはクラスまたはインタフェースが単純なバイナリ名(§4.2.1)を持ち、無名パッケージ(JLS§7.4.2)を体現するランタイム・パッケージのメンバーである場合に発生する可能性があります。 いずれの場合も、クラスまたはインタフェースは、クラス・ローダーに暗黙的にバインドされる特別なランタイム・モジュールのメンバーとして扱われます。 この特別なランタイム・モジュールは、クラス・ローダーの名前なしモジュールと呼ばれます。 クラスまたはインタフェースのランタイム・パッケージは、クラス・ローダーの名前のないモジュールに関連付けられます。 名前のないモジュールには、他のランタイム・モジュールとの相互運用性を最大限にするために設計された特別なルールが次のようにあります。
クラス・ローダーの名前のないモジュールは、同じクラス・ローダーにバインドされている他のすべてのランタイム・モジュールとは異なります。
クラス・ローダーの名前のないモジュールは、他のクラス・ローダーにバインドされているすべてのランタイム・モジュール(名前のないモジュールを含む)とは異なります。
すべての名前のないモジュールは、すべてのランタイム・モジュールを読み取ります。
すべての名前のないモジュールは、それ自体に関連付けられたすべてのランタイム・パッケージをすべてのランタイム・モジュールにエクスポートします。
クラスまたはインタフェースをリンクするには、必要に応じて、そのクラスまたはインタフェース、その直接スーパークラス、その直接スーパーインタフェース、および要素タイプ(配列型の場合)を検証および準備します。 リンクには、クラスまたはインタフェースでのシンボリック参照の解決も含まれますが、必ずしもクラスまたはインタフェースが検証および準備されるのと同時に行う必要はありません。
この仕様では、次のすべてのプロパティが保持されていれば、アクティビティのリンク(および再帰、ロード)がいつ行われるかについて、実装の柔軟性を確保できます。
クラスまたはインタフェースは、リンクされる前に完全にロードされます。
クラスまたはインタフェースは、初期化される前に完全に検証および準備されます。
リンケージ中に検出されたエラーは、プログラムが直接的または間接的に、エラーに関係するクラスまたはインタフェースへのリンクを必要とするようなアクションを実行した時点でスローされます。
動的に計算された定数へのシンボリック参照は、(i) ldc、ldc_w、またはそれを参照する ldc2_w命令が実行されるまで、または(ii)静的引数として参照するブートストラップメソッドが呼び出されるまで解決されません。
動的計算されたコール・サイトへのシンボリック参照は、静的引数として参照するブートストラップ・メソッドが呼び出されるまで解決されません。
たとえば、Java Virtual Machine実装では、レイジー・リンケージ方式を選択できます。この場合、クラスまたはインタフェース内の各シンボリック参照(前述のシンボリック参照以外)は、使用時に個別に解決されます。 あるいは、クラスまたはインタフェースが検証されるときにすべてのシンボリック参照が一度に解決される「即時」リンケージ戦略を実装が選択することもできます。 これは、一部の実装で、クラスまたはインタフェースが初期化された後も解決プロセスが続く場合があることを意味します。 どちらの方法に従っても、解決中に検出されたエラーは、(直接的または間接的に)クラスまたはインタフェースへのシンボリック参照を使用するプログラム内のポイントでスローされる必要があります。
リンクには新しいデータ構造の割当てが含まれるため、OutOfMemoryErrorで失敗する可能性があります。
Verification (§4.10)は、クラスまたはインタフェースのバイナリ表現が構造的に正しいことを保証します(§4.9)。 検証により、追加のクラスおよびインタフェースがロードされる場合がありますが(§5.3)、検証または準備する必要はありません。
クラスまたはインタフェースのバイナリ表現が§4.9にリストされている静的制約または構造的制約を満たさない場合、クラスまたはインタフェースが検証される原因となったプログラム内のポイントでVerifyErrorがスローされる必要があります。
LinkageError (またはサブクラス)のインスタンスであるエラーがスローされたためにJava Virtual Machineがクラスまたはインタフェースを検証しようとして失敗した場合、後続のクラスまたはインタフェースの検証試行は、最初の検証試行の結果としてスローされたものと同じエラーで常に失敗します。
準備では、クラスまたはインタフェースの静的フィールドを作成し、それらのフィールドをデフォルト値に初期化します(§2.3、§2.4)。 この場合、Java Virtual Machineコードの実行は必要ありません。静的フィールドの明示的な初期化子は、準備ではなく初期化(§5.5)の一部として実行されます。
クラスまたはインタフェースCの準備中に、Java Virtual Machineではロード制約も課されます(§5.3.4)。
L1をCの定義ローダーにします。 スーパークラスまたはスーパーインタフェースD = <N2 (L2>)で宣言されたインスタンス・メソッドをオーバーライドできるCで宣言されたインスタンス・メソッドm (§5.4.5)ごとに、m (§4.3.3)の記述子で言及されたクラス名またはインタフェース名Nごとに、Java Virtual Machineによってロード制約NL1 = NL2が課されます。
スーパーインタフェースI = <N3、CのL3>で宣言された各インスタンス・メソッドmについて、C自体がmをオーバーライドできるインスタンス・メソッドを宣言していない場合、CおよびIのメソッドmに関してメソッド(§5.4.6)が選択されます。 D = <N2、L2>を、選択したメソッドを宣言するクラスまたはインタフェースにします。 mの記述子で指定されたクラス名またはインタフェース名Nごとに、Java Virtual Machineによってロード制約NL2 = NL3が課されます。
準備は作成後いつでも行うことができますが、初期化の前に完了する必要があります。
多くのJava Virtual Machine命令- anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield、putstatic - 実行時定数プール内のシンボリック参照に依存します。 これらの命令のいずれかを実行するには、シンボリック参照の解決策が必要です。
解決法は、実行時定数プール内のシンボリック参照から1つ以上の具体的な値を動的に決定するプロセスです。 最初は、実行時定数プール内のすべてのシンボリック参照が解決されません。
(i)クラスまたはインタフェース、(ii)フィールド、(iii)メソッド、(iv)メソッド・タイプ、(v)メソッド・ハンドル、(vi)動的に計算された定数に対する未解決のシンボリック参照の解決は、§5.4.3.1から§5.4.3.5までのルールに従って進みます。 これらのセクションの最初の3つのセクションでは、シンボリック参照が表示される実行時定数プールのクラスまたはインタフェースには Dというラベルが付けられています。 次に、
シンボリック参照の解決中にエラーが発生しなかった場合、解決は成功します。
後続のシンボリック参照の解決の試みは、常に簡単な方法で成功し、最初の解決によって生成される同じエンティティになります。 シンボリック参照が動的に計算された定数に対するものである場合、ブートストラップ・メソッドは後続の試行では再実行されません。
シンボリック参照の解決中にエラーが発生した場合は、(i) IncompatibleClassChangeError (またはサブクラス)のインスタンス、(ii)ブートストラップ・メソッドの解決または呼出しから発生したError (またはサブクラス)のインスタンス、または(iii)クラスのロードに失敗したか、ローダー制約に違反したために発生した LinkageError (またはサブクラス)のインスタンスです。 エラーは、(直接的または間接的に)シンボリック参照を使用するプログラム内のポイントでスローされる必要があります。
その後のシンボリック参照の解決の試行は、最初の解決の試行の結果としてスローされたものと同じエラーで常に失敗します。 シンボリック参照が動的に計算された定数に対するものである場合、ブートストラップ・メソッドは後続の試行では再実行されません。
解決時の初期試行で発生したエラーは、後続の試行で再度スローされるため、1つのモジュール内のクラスで、実行時定数プール内のシンボリック参照の解決を介して、エクスポートされないpublic型にアクセスしようとします。別のモジュールでは、Java SE Platform APIを使用してクラスの最初の試行後にpublic型のパッケージを動的にエクスポートする場合でも、、アクセスできない型(§5.4.4)を示す同じエラーが常に返されます。
動的に計算されたコール・サイトへの未解決のシンボリック参照の解決は、§5.4.3.6に記載されている規則に従って進められます。 次に、
シンボリック参照の解決中にエラーが発生しなかった場合、解決は解決が必要なclassファイル内の命令に対してのみ成功します。 この命令は、必ずinvokedynamicのopコードを持ちます。
続いて、classファイル内の命令によってシンボリック参照を解決しようとすると、常にわずかに成功し、最初の解決によって生成される同じエンティティーになります。 ブートストラップ・メソッドは、後続の試行では再実行されません。
シンボリック参照は、前述のinvokedynamic命令と同じ実行時定数プール内のエントリを示す任意のopcodeのclassファイル内の他のすべての命令に対して、まだ解決されていません。
シンボリック参照の解決中にエラーが発生した場合は、(i) IncompatibleClassChangeError (またはサブクラス)のインスタンス、(ii)ブートストラップ・メソッドの解決または呼出しから発生したError (またはサブクラス)のインスタンス、または(iii)クラスのロードに失敗したか、ローダー制約に違反したために発生した LinkageError (またはサブクラス)のインスタンスです。 エラーは、(直接的または間接的に)シンボリック参照を使用するプログラム内のポイントでスローされる必要があります。
その後、classファイル内の同じ命令によってシンボリック参照を解決しようとすると、最初の解決の試みの結果としてスローされたものと同じエラーで常に失敗します。 ブートストラップ・メソッドは、後続の試行では再実行されません。
シンボリック参照は、前述のinvokedynamic命令と同じ実行時定数プール内のエントリを示す任意のopcodeのclassファイル内の他のすべての命令に対して、まだ解決されていません。
前述の手順の中には、シンボリック参照を解決する際に追加のリンク・チェックが必要なものがあります。 たとえば、getfield命令が動作するフィールドへのシンボリック参照を正常に解決するためには、§5.4.3.2に示されているフィールド解決ステップを完了するだけでなく、フィールドがstaticでないこともチェックする必要があります。 staticフィールドの場合は、リンク例外をスローする必要があります。
特定のJava Virtual Machine命令の実行に固有のチェックによって生成された例外のリンクは、その命令の説明に示されており、この解決に関する一般的な説明では説明していません。 このような例外は、解決ではなくJava Virtual Machine命令の実行の一部として記述されていますが、解決の失敗と引き続き適切にみなされます。
未解決のシンボリック参照を Dから Nで示されるクラスまたはインタフェース Cに解決するには、次の手順を実行します。
Dの定義ローダーは、ロードに使用され、Nで示されるクラスまたはインタフェースを作成します。 このクラスまたはインタフェースは Cです。 プロセスの詳細は §5.3を参照してください。
ロードの失敗によってスローされ、それによってCが作成される可能性のある例外は、クラスおよびインタフェース解決の失敗の結果としてスローされます。
Cが配列クラスで、その要素タイプがreference型の場合、要素型を表すクラスまたはインタフェースへのシンボリック参照は、§5.4.3.1のアルゴリズムを再帰的に呼び出すことによって解決されます。
最後に、アクセス制御がDからC (§5.4.4)へのアクセスに適用されます。
ステップ1とステップ2が成功してもステップ3が失敗した場合でも、Cは有効であり、使用できます。 ただし、解決は失敗し、Dは Cにアクセスできません。
未解決のシンボリック参照をDからクラスまたはインタフェースCのフィールドに解決するには、フィールド参照によって指定されたCへのシンボリック参照を最初に解決する必要があります(§5.4.3.1)。 したがって、フィールド解決の失敗の結果としてクラスまたはインタフェース参照の解決に失敗した結果としてスローできる例外は、すべてスローできます。 Cへの参照を正常に解決できる場合は、フィールド参照自体の解決の失敗に関する例外をスローできます。
フィールド参照を解決する場合、フィールド解決では最初にCの参照フィールドとそのスーパークラスが検索されます。
Cがフィールド参照で指定された名前と記述子を持つフィールドを宣言すると、フィールド検索は成功します。 宣言されたフィールドは、フィールド検索の結果です。
それ以外の場合、フィールド検索は、指定されたクラスまたはインタフェース Cの直接スーパーインタフェースに再帰的に適用されます。
それ以外の場合、Cにスーパークラス Sがある場合、フィールド検索は Sに再帰的に適用されます。
それ以外の場合、フィールド検索は失敗します。
次に、フィールド変換の結果が決定されます。
フィールド検索に失敗すると、フィールド解決によってNoSuchFieldErrorがスローされます。
それ以外の場合、フィールド検索は成功しました。 アクセス制御は、Dからフィールド検索の結果であるフィールド(§5.4.4)へのアクセスに適用されます。 次に、
アクセス制御が失敗した場合、フィールドの解決は同じ理由で失敗します。
それ以外の場合、アクセス制御は成功しました。 ロード制約は、次のように強制されます。
<E、L1>を、参照フィールドが実際に宣言されるクラスまたはインタフェースにします。 L2をDの定義ローダーにします。
参照フィールドの記述子(§4.3.2)によって記述されたクラス名またはインタフェース名Nの場合、Java Virtual Machineでは、ロード制約NL1 = NL2 (§5.3.4)が課されます。
この制約を適用すると、ロード制約に違反すると、フィールド解決は失敗します。 それ以外の場合は、フィールド解決が成功します。
未解決のシンボリック参照をDからクラスCのメソッドに解決するには、メソッド参照によって指定されたCへのシンボリック参照が最初に解決されます(§5.4.3.1)。 したがって、クラス参照の解決の失敗の結果としてスローできる例外は、メソッド解決の失敗の結果としてスローできます。 Cへの参照を正常に解決できる場合は、メソッド参照自体の解決に関連する例外をスローできます。
メソッド参照を解決する場合:
Cがインタフェースの場合、メソッド解決はIncompatibleClassChangeErrorをスローします。
それ以外の場合、メソッド解決では、Cおよびそのスーパークラスで参照されるメソッドの検索が試行されます。
Cがメソッド参照で指定された名前を持つメソッドを1つだけ宣言し、その宣言が署名多相メソッド(§2.9.3)である場合、メソッド検索は成功します。 メソッド参照によって指定された記述子は、未解決のシンボリック参照をメソッド型(§5.4.3.5)に解決するかのように解決されます。
解決されたメソッドは、シグネチャ多相メソッド宣言です。 Cは、メソッド参照で指定されたディスクリプタを使用してメソッドを宣言する必要はありません。
それ以外の場合、Cがメソッド参照で指定された名前と記述子を持つメソッドを宣言すると、メソッド検索は成功します。
それ以外の場合、Cにスーパークラスがある場合、メソッド解決のステップ2は、Cの直接スーパークラスで再帰的に呼び出されます。
それ以外の場合、メソッド解決は、指定されたクラスCのスーパーインタフェース内で参照されるメソッドの検索を試みます。
メソッド参照で指定された名前および記述子のCの最大固有のスーパーインタフェース・メソッドに、ACC_ABSTRACTフラグが設定されていないメソッドが1つのみ含まれている場合、このメソッドが選択され、メソッド検索が成功します。
それ以外の場合、Cのスーパーインタフェースが、ACC_PRIVATEフラグもACC_STATICフラグも設定されていないメソッド参照によって指定された名前と記述子を持つメソッドを宣言すると、これらのいずれかが任意に選択され、メソッド検索が成功します。
それ以外の場合、メソッドの検索は失敗します。
特定のメソッド名およびディスクリプタに対するクラスまたはインタフェースCの最大固有のスーパーインタフェース・メソッドは、次のすべてに当てはまるメソッドです。
このメソッドは、Cのスーパーインタフェース(直接または間接)で宣言されます。
このメソッドは、指定された名前と記述子で宣言されます。
このメソッドには、ACC_PRIVATEフラグもACC_STATICフラグも設定されていません。
このメソッドがインタフェース Iで宣言されている場合、Iのサブインタフェースで宣言されている、指定された名前と記述子を持つ Cのほかの最大固有のスーパーインタフェースメソッドはありません。
メソッド解決の結果は、次のように決定されます。
メソッドのルックアップに失敗すると、メソッドの解決によってNoSuchMethodErrorがスローされます。
それ以外の場合、メソッドの検索は成功しました。 アクセス制御は、Dからメソッド検索の結果であるメソッド(§5.4.4)へのアクセスに適用されます。 次に、
アクセス制御が失敗した場合、メソッドの解決も同じ理由で失敗します。
それ以外の場合、アクセス制御は成功しました。 ロード制約は、次のように強制されます。
<E、L1>を、参照されるメソッドmが実際に宣言されるクラスまたはインタフェースにします。 L2をDの定義ローダーにします。
参照されたメソッドの記述子(§4.3.3)によって記述されたクラス名またはインタフェース名Nごとに、Java Virtual Machineはロード制約NL1 = NL2 (§5.3.4)を課します。
これらの制約を適用すると、ロード制約に違反すると、メソッドの解決は失敗します。 それ以外の場合、メソッドの解決は成功します。
解決でクラスのスーパーインタフェース内のメソッドを検索する場合、最適な結果は、最大限固有のabstract以外のメソッドを識別することです。 このメソッドは、メソッド選択によって選択される可能性があるため、クラス・ローダー制約を追加することをお薦めします。
それ以外の場合、結果は非決定的です。 これは新しいことではありません: 『Java®Virtual Machine Specification』では、どの方法が選択されているか、および「タイ」がどのように壊れているかを正確に特定したことはありません。 Java SE 8より前は、これはほとんど目に見えない区別でした。 ただし、Java SE 8以降では、インタフェース・メソッドのセットがより異種になるため、非決定的な動作の問題を回避するには注意が必要です。 このため、:
privateおよびstaticであるスーパーインタフェース・メソッドは、解決によって無視されます。 これは、このようなインタフェース・メソッドが継承されないJavaプログラミング言語と一致しています。
解決されたメソッドによって制御される動作は、メソッドがabstractかどうかに依存しません。
解決の結果がabstractメソッドの場合、参照されるクラスCはabstract以外である可能性があることに注意してください。 Cをabstractにする必要があると、スーパーインタフェース・メソッドの非決定的な選択と競合します。 かわりに、resolutionは、呼び出されたオブジェクトのランタイム・クラスにメソッドの具体的な実装があることを前提としています。
未解決のシンボリック参照をDからインタフェースCのインタフェース・メソッドに解決するには、インタフェース・メソッド参照によって指定されたCへのシンボリック参照が最初に解決されます(§5.4.3.1)。 したがって、インタフェース・メソッド解決の失敗の結果としてスローされる例外は、インタフェース・メソッド解決の失敗の結果としてスローできます。 Cへの参照を正常に解決できる場合は、インタフェース・メソッド参照自体の解決に関連する例外をスローできます。
インタフェース・メソッド参照を解決する場合:
Cがインタフェースでない場合は、インタフェース・メソッドの解決によってIncompatibleClassChangeErrorがスローされます。
それ以外の場合、Cがインタフェース・メソッド参照で指定された名前および記述子を持つメソッドを宣言すると、メソッド検索は成功します。
それ以外の場合、クラスObjectが、ACC_PUBLICフラグが設定され、ACC_STATICフラグが設定されていないインタフェース・メソッド参照によって指定された名前および記述子を持つメソッドを宣言すると、メソッド検索は成功します。
それ以外の場合、メソッド参照で指定された名前および記述子に対するCの最大固有のスーパーインタフェース・メソッド(§5.4.3.3)に、ACC_ABSTRACTフラグが設定されていないメソッドが1つだけ含まれていると、このメソッドが選択され、メソッドの検索が成功します。
それ以外の場合、Cのスーパーインタフェースが、ACC_PRIVATEフラグもACC_STATICフラグも設定されていないメソッド参照によって指定された名前と記述子を持つメソッドを宣言すると、これらのいずれかが任意に選択され、メソッド検索が成功します。
それ以外の場合、メソッドの検索は失敗します。
インタフェース・メソッド解決の結果は、次のように決定されます。
メソッドのルックアップに失敗すると、インタフェース・メソッドの解決によってNoSuchMethodErrorがスローされます。
それ以外の場合、メソッドの検索は成功しました。 アクセス制御は、Dからメソッド検索の結果であるメソッド(§5.4.4)へのアクセスに適用されます。 次に、
アクセス制御が失敗した場合、インタフェース・メソッドの解決も同じ理由で失敗します。
それ以外の場合、アクセス制御は成功しました。 ロード制約は、次のように強制されます。
<E、L1>を、参照されるインタフェース・メソッドmが実際に宣言されるクラスまたはインタフェースにします。 L2をDの定義ローダーにします。
参照されたメソッドの記述子(§4.3.3)によって記述されたクラス名またはインタフェース名Nごとに、Java Virtual Machineはロード制約NL1 = NL2 (§5.3.4)を課します。
これらの制約を適用すると、ロード制約に違反すると、インタフェース・メソッドの解決は失敗します。 それ以外の場合は、インタフェース・メソッドの解決が成功します。
インタフェース・メソッドの解決では、インタフェースCのprivateメソッドを選択する可能性があるため、アクセス制御が必要です。 (Java SE 8より前は、インタフェース・メソッド解決の結果は、クラスObjectのpublic以外のメソッドまたはクラスObjectのstaticメソッドである可能性があります。このような結果は、Javaプログラミング言語の継承モデルと一致せず、Java SE 8以上では許可されません。)
未解決のシンボリック参照をメソッド型に解決するには、メソッド記述子(§4.3.3)で名前が記述されているクラスおよびインタフェース(§5.4.3.1)への未解決のシンボリック参照が、記述されている順序で解決されるかのようになります。
したがって、クラスまたはインタフェースへの参照の解決に失敗した結果としてスローできる例外は、メソッド・タイプの解決に失敗した結果としてスローされます。
メソッド型解決の成功の結果は、メソッド記述子を表すjava.lang.invoke.MethodTypeのインスタンスに対するreferenceです。
メソッド・タイプの解決は、実行時定数プールに、メソッド記述子に示されたクラスおよびインタフェースへのシンボリック参照が実際に含まれているかどうかに関係なく行われます。 また、解決は未解決のシンボリック参照で発生するとみなされるため、1つのメソッド・タイプの解決に失敗しても、適切なクラスおよびインタフェースを後でロードできる場合は、同じテキスト・メソッド記述子を持つ別のメソッド・タイプの解決が後で失敗するわけではありません。
メソッド・ハンドルに対する未解決のシンボリック参照の解決はより複雑です。 Java Virtual Machineによって解決される各メソッド・ハンドルには、メソッド・ハンドルのkindで示されるバイトコード動作と呼ばれる同等の命令シーケンスがあります。 9種類のメソッド・ハンドルの整数値および説明を、表5.4.3.5-Aに示します。
フィールドまたはメソッドへの命令シーケンスによるシンボリック参照は、C.x:Tによって示されます。ここで、xおよびTは、フィールドまたはメソッドの名前および記述子(§4.3.2、§4.3.3)であり、Cは、フィールドまたはメソッドが見つかるクラスまたはインタフェースです。
表5.4.3.5-A. メソッド・ハンドルのバイトコード動作
| Kind | 説明 | 解釈 |
|---|---|---|
| 1 | REF_getField |
getfield C.f:T |
| 2 | REF_getStatic |
getstatic C.f:T |
| 3 | REF_putField |
putfield C.f:T |
| 4 | REF_putStatic |
putstatic C.f:T |
| 5 | REF_invokeVirtual |
invokevirtual C.m:(A*)T |
| 6 | REF_invokeStatic |
invokestatic C.m:(A*)T |
| 7 | REF_invokeSpecial |
invokespecial C.m:(A*)T |
| 8 | REF_newInvokeSpecial |
new C; dup; invokespecial C. |
| 9 | REF_invokeInterface |
invokeinterface C.m:(A*)T |
MHを、解決されるメソッド・ハンドル(§5.1)へのシンボリック参照にします。 追記:
Rを、MHで指定されたフィールドまたはメソッドへのシンボリック参照にします。
たとえば、Rは、C . f (種類1のバイトコード動作)へのシンボリック参照であり、C . <init> (種類8のバイトコード動作)へのシンボリック参照です。
Cは、Rによって参照されるクラス、インタフェース、または配列型になります。
Tは、Rのフィールド記述子で指定された型、または Rのメソッド記述子で指定された戻り型になります。 A*は、Rのメソッド記述子で指定されたパラメータ型の順序(おそらく空)になります。
MHを解決するには、MHのバイトコード動作のクラス、インタフェース、フィールドおよびメソッドへのすべてのシンボリック参照が、次の3つのステップを使用して解決されます。
Rは解決されます。 これは、MHのバイトコード動作が1、2、3または4である場合にフィールド解決(§5.4.3.2)によって発生し、MHのバイトコード動作が5、6、7または8である場合にメソッド解決(§5.4.3.3)によって発生し、MHのバイトコード動作が9である場合にインタフェース・メソッド解決(§5.4.3.4)によって発生するかのように発生します。
Rを解決した結果には、次の制約が適用されます。 これらの制約は、関連するバイトコード動作の命令シーケンスの検証または実行中に強制される制約に対応します。
MHのバイトコード動作が7(REF_invokeSpecial)の場合、Cは、現在のクラスまたはインタフェース、現在のクラスのスーパークラス、現在のクラスまたはインタフェースの直接スーパーインタフェース、またはObjectである必要があります。
MHのバイトコード動作が8(REF_newInvokeSpecial)の場合、RはクラスCで宣言されたインスタンス初期化メソッドに解決される必要があります。
Rがprotectedメンバーに解決される場合、MHのバイトコード動作の種類に応じて、次のルールが適用されます。
1、3および5 (REF_getField、REF_putFieldおよびREF_invokeVirtual)の場合: C.fまたはC.mがprotectedフィールドまたはメソッドに解決され、Cが現在のクラスとは異なるランタイム・パッケージにある場合、Cは現在のクラスのサブクラスである必要があります。
種類8 (REF_newInvokeSpecial)の場合: C . <init>がprotectedメソッドに解決された場合、Cは現在のクラスと同じランタイム・パッケージで宣言する必要があります。
Rは、MHのバイトコード動作の種類に応じて、staticまたは非staticメンバーに解決される必要があります。
1、3、5、7および9(REF_getField、REF_putField、REF_invokeVirtual、REF_invokeSpecialおよびREF_invokeInterface)の場合: C.fまたはC.mは、static以外のフィールドまたはメソッドに解決される必要があります。
2、4および6(REF_getStatic、REF_putStaticおよびREF_invokeStatic)の場合: C.fまたはC.mは、staticフィールドまたはメソッドに解決される必要があります。
java.lang.invoke.MethodTypeのインスタンスへの参照は、表5.4.3.5-Bで指定されているメソッド記述子を含むメソッド型への未解決のシンボリック参照を解決することによって、MHの型として取得されます。
これは、メソッド・ハンドルへのシンボリック参照に、解決されたメソッド・ハンドルが最終的に持つメソッド・タイプへのシンボリック参照が含まれているかのようになります。 メソッド・タイプの詳細な構造は、表5.4.3.5-Bを調べて取得します。
表5.4.3.5-B. メソッドハンドルのメソッド記述子
| Kind | 説明 | メソッド記述子 |
|---|---|---|
| 1 | REF_getField |
(C)T |
| 2 | REF_getStatic |
()T |
| 3 | REF_putField |
(C,T)V |
| 4 | REF_putStatic |
(T)V |
| 5 | REF_invokeVirtual |
(C,A*)T |
| 6 | REF_invokeStatic |
(A*)T |
| 7 | REF_invokeSpecial |
(C,A*)T |
| 8 | REF_newInvokeSpecial |
(A*)C |
| 9 | REF_invokeInterface |
(C,A*)T |
ステップ1および3では、メソッド・ハンドル解決の失敗の結果として、クラス、インタフェース、フィールドまたはメソッドへのシンボリック参照の解決に失敗した結果としてスローできる例外をスローできます。 ステップ2では、指定された制約が原因で障害が発生すると、IllegalAccessErrorが原因でメソッド・ハンドルの解決が失敗します。
目的は、メソッド・ハンドルの解決は、Java Virtual Machineがバイトコード動作でシンボリック参照を正常に検証および解決するのとまったく同じ状況で実行できることです。 特に、private、protectedおよびstaticメンバーに対するメソッド・ハンドルは、対応する通常アクセスが合法であるクラスに正確に作成できます。
メソッド・ハンドルの解決が成功した結果は、メソッド・ハンドルMHを表すjava.lang.invoke.MethodHandleのインスタンスに対するreferenceです。
このjava.lang.invoke.MethodHandleインスタンスの型記述子は、前述のメソッド・ハンドル解決の3番目のステップで生成されるjava.lang.invoke.MethodTypeインスタンスです。
メソッド・ハンドルの型記述子は、メソッド・ハンドルに対するjava.lang.invoke.MethodHandleのinvokeExactへの有効なコールが、バイトコード動作とまったく同じスタック効果を持つようになります。 有効な引数のセットに対してこのメソッド・ハンドルをコールすると、まったく同じ効果が得られ、対応するバイトコード動作と同じ結果(ある場合)が返されます。
Rによって参照されるメソッドにACC_VARARGSフラグが設定されている場合(§4.6)、java.lang.invoke.MethodHandleインスタンスは可変アリティ・メソッド・ハンドルであり、それ以外の場合は固定アリティ・メソッド・ハンドルです。
可変アリティ・メソッド・ハンドルは、invokeを介して呼び出されたときに引数リスト・ボクシング(JLS§15.12.4.2)を実行しますが、invokeExactに対する動作は、ACC_VARARGSフラグが設定されていないかのようになります。
Rによって参照されるメソッドにACC_VARARGSフラグが設定されていて、A*が空のシーケンスであるか、A*の最後のパラメータ型が配列型でない場合、メソッド・ハンドルの解決ではIncompatibleClassChangeErrorがスローされます。 つまり、可変アリティ・メソッド・ハンドルの作成は失敗します。
メソッド・タイプまたはメソッド・ハンドルをインターンするには、Java Virtual Machineの実装は必要ありません。 つまり、構造的に同じメソッド・タイプまたはメソッド・ハンドルへの2つの異なるシンボリック参照は、それぞれjava.lang.invoke.MethodTypeまたはjava.lang.invoke.MethodHandleの同じインスタンスには解決されない場合があります。
Java SE Platform APIのjava.lang.invoke.MethodHandlesクラスでは、バイトコード動作のないメソッド・ハンドルを作成できます。 これらの動作は、それらを作成するjava.lang.invoke.MethodHandlesのメソッドによって定義されます。 たとえば、メソッド・ハンドルが呼び出されると、まずその引数値に変換を適用してから、変換された値を別のメソッド・ハンドルの呼出しに提供し、その呼出しから戻された値に変換を適用してから、変換された値を独自の結果として戻すことができます。
未解決のシンボリック参照 Rを動的に計算された定数または呼び出しサイトに解決するには、3つのタスクがあります。 まず、Rを調べて、ブートストラップ・メソッドとして機能するコードと、そのコードに渡される引数を特定します。 次に、引数が配列にパッケージ化され、bootstrapメソッドが呼び出されます。 3つ目は、bootstrapメソッドの結果が検証され、解決の結果として使用されることです。
最初のタスクには、次のステップが含まれます。
Rは、ブートストラップ・メソッド・ハンドルへのシンボリック参照を提供します。 ブートストラップ・メソッド・ハンドルが解決され(§5.4.3.5)、referenceがjava.lang.invoke.MethodHandleのインスタンスに対して取得されます。
メソッド・ハンドルへのシンボリック参照の解決に失敗した結果としてスローされる可能性がある例外は、このステップでスローできます。
Rが動的に計算された定数へのシンボリック参照である場合は、Dをブートストラップメソッドハンドルの型記述子にしてください。 (つまり、Dはjava.lang.invoke.MethodTypeのインスタンスに対するreferenceです。) Dによって示される最初のパラメータ型はjava.lang.invoke.MethodHandles.Lookupである必要があります。そうでない場合、解決はBootstrapMethodErrorで失敗します。 過去の理由から、動的に計算されたコール・サイトのブートストラップ・メソッド・ハンドルも同様に制約されません。
Rが動的に計算された定数へのシンボリック参照である場合、フィールド記述子が与えられます。
フィールド記述子がプリミティブ型を示している場合、その型を表す事前定義済のClassオブジェクトに対するreferenceが取得されます(ClassクラスのメソッドisPrimitiveを参照)。
それ以外の場合、フィールド記述子はクラスまたはインタフェース型、あるいは配列型を示します。 フィールド記述子によって示される型を表すClassオブジェクトに対するreferenceが取得されます。これは、フィールド記述子によって示される型に対応する名前を持つクラスまたはインタフェース(§5.4.3.1)への未解決のシンボリック参照を解決する場合と同様です。
クラスまたはインタフェースへのシンボリック参照の解決に失敗した結果としてスローできる例外は、このステップでスローできます。
Rが動的に計算されたコール・サイトへのシンボリック参照である場合、メソッド記述子が与えられます。
java.lang.invoke.MethodTypeのインスタンスに対するreferenceは、メソッド記述子と同じパラメータおよび戻り型を持つメソッド型(§5.4.3.5)への未解決のシンボリック参照を解決することによって取得されます。
メソッド・タイプへのシンボリック参照の解決に失敗した結果としてスローできる例外は、このステップでスローできます。
Rは、アプリケーション固有のメタデータをブートストラップ・メソッドと通信する0個以上の静的引数を提供します。 各静的引数 Aは、次のように Rで指定された順序で解決されます。
Aが文字列定数の場合、クラスStringのインスタンスのreferenceが取得されます。
Aが数値定数の場合、数値を表すオブジェクトへのreferenceは、次の手順で取得されます。
vを数値定数の値にし、Tを数値定数の型に対応するフィールド記述子にします。
MHは、java.lang.invoke.MethodHandlesのidentityメソッドを、クラスObjectを表す引数とともに呼び出すことによって生成されるメソッド・ハンドルになります。
オブジェクトに対するreferenceは、メソッド記述子(T)Ljava/lang/Object;を持つ呼出しMH.invoke(v)によって取得されます。
Aが、プリミティブ型Tを示すフィールド記述子を持つ動的に計算された定数へのシンボリック参照である場合、Aが解決され、プリミティブ値vが生成されます。 vおよびTを指定すると、数値定数の前述の手順に従って、referenceがオブジェクト・エンコーディングvに対して取得されます。
Aがほかの種類のシンボリック参照である場合、結果は Aを解決した結果になります。
ランタイム定数プール内のシンボリック参照の中で、動的に計算された定数へのシンボリック参照は、BootstrapMethods属性(§4.7.23)を介して構文的に自身を参照できるconstant_poolエントリから導出されるため、特別なものです。 ただし、Java Virtual Machineでは、それ自体に依存する動的に計算された定数へのシンボリック参照の解決(つまり、独自のブートストラップ・メソッドへの静的引数)はサポートされていません。 したがって、Rと Aの両方が動的に計算される定数のシンボリック参照である場合、Aが Rと同じである場合、または Aが Rを直接的または間接的に参照する静的引数を指定すると、Rの再解決が必要となる時点で StackOverflowErrorで解決が失敗します。
初期化されていないクラス間で循環が許可されるクラス初期化(§5.5)とは異なり、分解能では、動的に計算される定数へのシンボリック参照の循環は許可されません。 解決の実装でスタックを再帰的に使用する場合、StackOverflowErrorは自然に発生します。 そうでない場合は、無限にループしたり、動的に計算された定数のデフォルト値を返したりするのではなく、サイクルを検出するために実装が必要です。
ブートストラップ・メソッドの本体が、現在解決中の動的に計算された定数を参照している場合、同様のサイクルが発生する可能性があります。 これは常にinvokedynamicブートストラップで可能であり、解決に特別な処置は必要ありません。再帰的なinvokeWithArgumentsコールは、自然にStackOverflowErrorにつながります。
このステップでは、シンボリック参照の解決に失敗した結果としてスローできる例外をスローできます。
ブートストラップ・メソッド・ハンドルを起動する2番目のタスクには、次のステップが含まれます。
配列は、コンポーネント・タイプObjectおよび長さn+3で割り当てられます。ここで、nは、R (n ≥ 0)で指定された静的引数の数です。
配列のゼロ・コンポーネントは、java.lang.invoke.MethodHandlesのlookupメソッドの呼出しによって生成される、Rが発生するクラスのjava.lang.invoke.MethodHandles.Lookupのインスタンスに対してreferenceに設定されます。
配列の最初のコンポーネントは、N (Rで指定された修飾されていない名前)を示すStringのインスタンスに対してreferenceに設定されます。
配列の2番目のコンポーネントは、Rによって指定されたフィールド記述子またはメソッド記述子に対して以前に取得されたClassまたはjava.lang.invoke.MethodTypeのインスタンスに対してreferenceに設定されます。
配列の後続のコンポーネントは、Rの静的引数(存在する場合)の解決から以前に取得したreferenceに設定されます。 referenceは、対応する静的引数がRによって指定されるのと同じ順序で配列に表示されます。
Java Virtual Machineの実装では、配列の割当てをスキップでき、監視可能な動作を変更せずに、引数をブートストラップ・メソッドに直接渡すことができます。
ブートストラップ・メソッド・ハンドルは、呼出しBMH.invokeWithArguments(args)のように呼び出されます。ここで、BMHはブートストラップ・メソッド・ハンドル、argsは前述の配列です。
java.lang.invoke.MethodHandleのinvokeWithArgumentsメソッドの動作により、ブートストラップ・メソッド・ハンドルの型記述子が引数のランタイム型と完全に一致している必要はありません。 たとえば、ブートストラップ・メソッド・ハンドルの2番目のパラメータ・タイプ(前述の配列の最初のコンポーネントで指定されている修飾されていない名前に対応)は、StringではなくObjectです。 ブートストラップ・メソッド・ハンドルが可変長の場合、一部の引数またはすべての引数を末尾の配列パラメータに収集できます。
この呼び出しは、このシンボリック参照の解決を試みているスレッド内で発生します。 このようなスレッドが複数ある場合は、ブートストラップ・メソッド・ハンドルを同時に呼び出すことができます。 グローバル・アプリケーション・データにアクセスするブートストラップ・メソッドは、競合状態に対する通常の予防措置をとる必要があります。
ErrorのインスタンスまたはErrorのサブクラスをスローして起動に失敗した場合、解決は失敗し、その例外が発生します。
Errorのインスタンスではない例外またはErrorのサブクラスをスローして起動が失敗した場合、解決は失敗し、原因がスローされた例外であるBootstrapMethodErrorがスローされます。
複数のスレッドがこのシンボリック参照に対してブートストラップ・メソッド・ハンドルを同時に起動する場合、Java Virtual Machineは1回の呼出しの結果を選択し、すべてのスレッドに視覚的にインストールします。 このシンボリック参照に対して実行されるその他のブートストラップ・メソッドは完了できますが、結果は無視されます。
3番目のタスクは、ブートストラップ・メソッド・ハンドルの起動によって生成されたreference、oを検証するために、次のとおりです。
Rが動的に計算された定数へのシンボリック参照である場合、oは、Rで指定されたフィールド記述子によって示される型であるT型に変換されます。
oの変換は、メソッド記述子(Ljava/lang/Object;)Tを持つ呼出しMH.invoke(o)によって行われるかのように行われます。MHは、java.lang.invoke.MethodHandlesのidentityメソッドを、クラスObjectを表す引数とともに呼び出すことによって生成されるメソッド・ハンドルです。
oの変換の結果は、解決の結果です。
NullPointerExceptionまたはClassCastExceptionをスローして変換が失敗した場合、解決はBootstrapMethodErrorで失敗します。
Rが動的に計算されたコール・サイトへのシンボリック参照である場合、oは次のすべてのプロパティを持つ場合に解決の結果になります。
oはnullではありません。
oは、java.lang.invoke.CallSiteのインスタンスまたはjava.lang.invoke.CallSiteのサブクラスです。
java.lang.invoke.CallSiteの型は、Rで指定されたメソッド記述子と意味的に等しくなります。
oにこれらのプロパティがない場合、解決はBootstrapMethodErrorで失敗します。
前述のステップの多くは、特定のメソッドの呼出しによる計算を実行します。 いずれの場合も、呼出しの動作は、invokestaticおよびinvokevirtualの仕様によって詳細に説明されています。 呼び出しは、スレッド内およびシンボリック参照 Rの解決を試みているクラスから発生します。 ただし、対応するメソッド参照はランタイム定数プール内に存在する必要はなく、特定のメソッドのオペランド・スタックは必ずしも使用されず、メソッドのCode属性のmax_stack項目の値が呼出しに強制されません。
アクセス制御は、クラス、インタフェース、フィールドまたはメソッドへの参照が許可されるように、解決(§5.4.3)中に適用されます。 アクセス制御は、指定されたクラス、インタフェース、フィールドまたはメソッドが参照クラスまたはインタフェースに対してアクセス可能である場合に成功します。
クラスまたはインタフェース Cは、次のいずれかに該当する場合にのみ、クラスまたはインタフェース Dからアクセスできます。
Cはpublicで、D (§5.3.6)と同じランタイム・モジュールのメンバーです。
Cはpublicで、Dとは異なるランタイム・モジュールのメンバーであり、Cのランタイム・モジュールはDのランタイム・モジュールによって読み取られ、Cのランタイム・モジュールはCのランタイム・パッケージをDのランタイム・モジュールにエクスポートします。
Cはpublicではなく、CとDは同じランタイム・パッケージのメンバーです。
CにDからアクセスできない場合、アクセス制御はIllegalAccessErrorをスローします。 それ以外の場合、アクセス制御は成功します。
フィールドまたはメソッドRは、次のいずれかに該当する場合にのみ、クラスまたはインタフェースDからアクセスできます。
Rはpublicです。
Rはprotectedで、クラスCで宣言され、DはCまたはC自体のサブクラスです。
さらに、Rが staticでない場合、Rへのシンボリック参照には、Tが Dのサブクラス、Dのスーパークラス、または D自体になるように、クラス Tへのシンボリック参照が含まれている必要があります。
Dの検証中に、TがDのスーパークラスであっても、protectedフィールド・アクセスまたはメソッド呼出しのターゲット参照は、DのインスタンスまたはDのサブクラス(§4.10.1.8)である必要があります。
Rは、protectedであるか、デフォルト・アクセスを持つ(つまり、publicもprotectedもprivateもない)もので、Dと同じランタイム・パッケージのクラスによって宣言されます。
Rはprivateであり、次のネストメート・テストに従って、Dと同じネストに属するクラスまたはインタフェースCによって宣言されます。
DからRにアクセスできない場合、アクセス制御はIllegalAccessErrorをスローします。 それ以外の場合、アクセス制御は成功します。
ネストは、privateメンバーへの相互アクセスを許可するクラスおよびインタフェースのセットです。 クラスまたはインタフェースの1つがネスト・ホストです。 NestMembers属性(§4.7.29)を使用して、ネストに属するクラスおよびインタフェースを列挙します。 それぞれが、NestHost属性(§4.7.28)を使用してネスト・ホストとして指定されます。 NestHost属性を持たないクラスまたはインタフェースは、それ自体がホストするネストに属します。NestMembers属性もない場合、このネストは、クラスまたはインタフェース自体のみで構成されるシングルトンです。
Java Virtual Machineは、クラスまたはインタフェースがロードされるときではなく、特定のクラスまたはインタフェースが属するネスト(つまり、クラスまたはインタフェースによって指定されたネスト・ホスト)をアクセス制御の一部として決定します。 Java SE Platform APIの特定のメソッドは、アクセス制御の前に、特定のクラスまたはインタフェースが属するネストを決定できます。その場合、Java Virtual Machineはアクセス制御中に事前に決定したことを尊重します。
クラスまたはインタフェースCがクラスまたはインタフェースDと同じネストに属しているかどうかを判断するために、ネストメイト・テストが適用されます。 Cと Dは、ネストメートテストが成功した場合にのみ、同じネストに属します。 nestmateテストは以下の通りです。
Cと Dが同じクラスまたはインタフェースである場合、ネストメートテストは成功します。
それ以外の場合は、次の手順が順番に実行されます。
Dのネストホストが以前に決定されている場合は、Hを Dのネストホストにします。 Dのネスト・ホストが以前に決定されていない場合は、次のアルゴリズムを使用して決定され、Hが生成されます。
以前に Cのネストホストが決定されている場合は、H'を Cのネストホストにします。 Cのネスト・ホストが以前に決定されていない場合は、次のアルゴリズムを使用して決定され、H'が生成されます。
Hと H'が比較されます。 Hと H'が同じクラスまたはインタフェースである場合、nestmateテストは成功します。 それ以外の場合、nestmateテストは失敗します。
クラスまたはインタフェースMのネスト・ホストは、次のように決定されます。
MにNestHost属性がない場合、Mは独自のネスト・ホストです。
それ以外の場合、MにはNestHost属性があり、そのhost_class_index項目はMの実行時定数プールへの索引として使用されます。 その索引のシンボリック参照が解決されます(§5.4.3.1)。
シンボリック参照の解決に失敗した場合、Mは独自のネスト・ホストになります。 クラスまたはインタフェース解決の失敗の結果としてスローされた例外は、再スローされません。
それ以外の場合、シンボリック参照の解決は成功します。 Hを解決済みのクラスまたはインタフェースにします。 Mのネスト・ホストは、次のルールによって決定されます。
次のいずれかがtrueの場合、Mは独自のネスト・ホストです。
Hは、Mと同じランタイム・パッケージ内にありません。
Hには、NestMembers属性がありません。
HにはNestMembers属性がありますが、Nという名前のクラスまたはインタフェースを参照するclasses配列にはエントリがありません。NはMの名前です。
それ以外の場合、HはMのネスト・ホストです。
次のすべてに該当する場合、インスタンス・メソッドmCは別のインスタンス・メソッドmAをオーバーライドできます。
mCは、mAと同じ名前および記述子を持ちます。
mCはACC_PRIVATEとしてマークされません。
次のいずれかがtrue:
mAはACC_PUBLICとマークされます。
mAはACC_PROTECTEDとマークされます。
mAは、ACC_PUBLICもACC_PROTECTEDもACC_PRIVATEもマークされず、(a) mAの宣言は、mCの宣言と同じランタイム・パッケージ内に存在するか、(b) mAがクラスAで宣言され、mCが宣言されている場合クラスCには、クラスBで宣言されたメソッドmBが存在し、CがBのサブクラスで、BがAのサブクラスで、mCがmBをオーバーライドでき、mBがmAをオーバーライドできます。
最後のケースのパート(b)では、デフォルト・アクセスを持つメソッドの「推移的オーバーライド」が可能です。 たとえば、パッケージ P内の次のクラス宣言があるとします。
public class A { void m() {} }
public class B extends A { public void m() {} }
public class C extends B { void m() {} }
別のパッケージ内の次のクラス宣言:
public class D extends P.C { void m() {} }
then:
B.mは、A.mをオーバーライドできます。
C.mは、B.mおよびA.mをオーバーライドできます。
D.mは、B.mおよび一時的にはA.mをオーバーライドできますが、C.mをオーバーライドすることはできません。
invokeinterfaceまたはinvokevirtual命令の実行中、メソッドは、(i)スタック上のオブジェクトの実行時型、および(ii)命令によって以前に解決されたメソッドに関して選択されます。 クラスまたはインタフェースCおよびメソッドmRに関してメソッドを選択するルールは、次のとおりです。
mRがACC_PRIVATEとマークされている場合は、そのメソッドが選択されます。
それ以外の場合、選択したメソッドは、次の参照プロシージャによって決定されます。
Cに、mR (§5.4.5)をオーバーライドできるインスタンス・メソッドmの宣言が含まれている場合、mが選択されたメソッドです。
それ以外の場合、Cにスーパークラスがある場合は、Cの直接スーパークラスから開始して、メソッドが見つかるか、それ以上のスーパークラスが存在しないまで、mRをオーバーライドできるインスタンス・メソッドの宣言の検索が実行されます。 メソッドが見つかった場合は、そのメソッドが選択されます。
それ以外の場合は、Cの最大固有のスーパーインタフェースメソッドが決定されます(§5.4.3.3)。 正確に1つがmRの名前および記述子と一致し、abstractでない場合、これは選択されたメソッドです。
このステップで選択した最大固有のスーパーインタフェース・メソッドは、mRをオーバーライドできます。これを明示的にチェックする必要はありません。
通常、Cはクラスですが、準備中にこれらのルールが適用される場合にはインタフェースになることがあります(§5.4.2)。
クラスまたはインタフェースの初期化では、ConstantValue属性値をそのstaticフィールドに割り当て、宣言されたクラスまたはインタフェース初期化メソッド(§2.9.2)を実行します。
クラスまたはインタフェース Cは、次の結果としてのみ初期化できます。
new、getstatic、putstaticまたはinvokestaticのうち、C (§new、§getstatic、§putstatic、§invokestatic)を参照する任意のJava Virtual Machine命令の実行。
新しい命令を実行すると、初期化されるクラスは命令によって参照されるクラスになります。
getstatic、putstaticまたはinvokestatic命令を実行すると、初期化されるクラスまたはインタフェースは、解決されたフィールドまたはメソッドを宣言するクラスまたはインタフェースになります。
2 (REF_getStatic)、4 (REF_putStatic)、6 (REF_invokeStatic)または8 (REF_newInvokeSpecial)のメソッド・ハンドルのメソッド・ハンドル解決(§5.4.3.5)の結果であるjava.lang.invoke.MethodHandleインスタンスの最初の起動。
これは、呼出しサイト指定子の継続的な解決の一部として、invokedynamic命令(§invokedynamic)に対してbootstrapメソッドが呼び出されたときに、bootstrapメソッドのクラスが初期化されることを意味します。
クラス・ライブラリ(§2.12)内の特定のリフレクティブ・メソッドの呼出し(たとえば、クラスClassまたはパッケージjava.lang.reflect内)。
Cがクラスの場合、そのサブクラスの1つを初期化します。
Cがabstract以外のstatic以外のメソッドを宣言するインタフェースである場合、Cを直接または間接的に実装するクラスの初期化。
Java Virtual Machineの起動時の初期クラスまたはインタフェースとしての指定(§5.2)。
初期化の前に、クラスまたはインタフェースをリンクする必要があります。つまり、検証、準備、およびオプションで解決する必要があります。
Java Virtual Machineはマルチスレッドであるため、クラスまたはインタフェースの初期化には慎重な同期が必要です。これは、他のスレッドが同時に同じクラスまたはインタフェースを初期化しようとしている可能性があるためです。 クラスまたはインタフェースの初期化は、そのクラスまたはインタフェースの初期化の一部として再帰的に要求される可能性もあります。 Java Virtual Machineの実装では、次の手順を使用して、同期および再帰的な初期化を処理します。 クラスまたはインタフェースがすでに検証および準備されており、クラスまたはインタフェースに次の4つの状況のいずれかを示す状態が含まれていることを前提としています。
このクラスまたはインタフェースは検証および準備されていますが、初期化されていません。
このクラスまたはインタフェースは、特定のスレッドによって初期化されています。
このクラスまたはインタフェースは完全に初期化され、すぐに使用できます。
このクラスまたはインタフェースは、初期化が試行されて失敗したため、間違った状態にあります。
初期化状態の正確な形式は、JVM実装の判断に任されます。
クラスまたはインタフェースCごとに、一意の初期化ロックLCがあります。 CからLCへのマッピングは、Java Virtual Machine実装の判断にも左右されます。 たとえば、LCは、CのClassオブジェクト、またはそのClassオブジェクトに関連付けられたモニターです。 Cを初期化する手順は次のとおりです。
Cの初期化ロックLCで同期します。 これには、現在のスレッドがLCを取得できるまで待機することが含まれます。
Cの初期化状態が、他のスレッドによってCの初期化が進行中であることを示す場合は、LCを解放し、進行中の初期化が完了したことを通知するまで現在のスレッドをブロックし、その時点でこの手順を繰り返します。
スレッドの割り込みステータスは、初期化プロシージャの実行によって影響を受けません。
Cの初期化状態が、現在のスレッドによって Cの初期化が進行中であることを示す場合、これは初期化の再帰的要求である必要があります。 LCを解放し、正常に完了します。
Cの初期化状態が Cがすでに初期化されていることを示している場合、それ以上のアクションは必要ありません。 LCを解放し、正常に完了します。
Cの初期化状態が間違った状態である場合、初期化はできません。 LCを解放し、NoClassDefFoundErrorをスローします。
それ以外の場合は、Cの初期化が現在のスレッドによって進行中であるという事実を記録し、LCを解放します。
次に、Cの各staticフィールドをConstantValue属性(§4.7.2)の定数値で初期化し、フィールドがClassFile構造体に表示される順序にします。
次に、Cがインタフェースではなくクラスである場合は、SCをそのスーパークラスにし、SI1、...、SInを、少なくとも1つの非abstract、非staticメソッドを宣言するCのすべてのスーパーインタフェース(直接または間接)にします。 スーパーインタフェースの順序は、Cによって直接実装される各インタフェースのスーパーインタフェース階層に対する再帰的列挙によって指定されます。 Cによって直接実装された各インタフェースI (Cのinterfaces配列の順序)について、Iを返す前に、Iのスーパーインタフェース(Iのinterfaces配列の順序)で列挙が繰り返されます。
リスト[ SC、 SI1、 ...、 SIn ]の各 Sについて、Sがまだ初期化されていない場合は、この手順全体を再帰的に Sに対して実行します。 必要に応じて、最初に Sを検証して準備します。
例外がスローされたためにSの初期化が突然完了した場合は、LCを取得し、Cにエラーのラベルを付け、すべての待機スレッドに通知し、LCを解放して、突然完了し、Sの初期化の結果と同じ例外をスローします。
次に、定義ローダーを問い合せて、アサーションがCに対して有効になっているかどうかを判断します。
次に、Cがクラスまたはインタフェース初期化メソッドを宣言する場合は、そのメソッドを実行します。
クラスまたはインタフェース初期化メソッドの実行が正常に完了した場合、またはCでクラスまたはインタフェース初期化メソッドが宣言されていない場合は、LCを取得し、Cに完全初期化のラベルを付け、すべての待機スレッドに通知し、LCを解放して、このプロシージャを通常どおり完了します。
それ以外の場合は、例外 Eをスローして、クラスまたはインタフェースの初期化メソッドが突然完了している必要があります。 EのクラスがErrorまたはそのサブクラスの1つでない場合は、引数としてEを使用してクラスExceptionInInitializerErrorの新しいインスタンスを作成し、次のステップでEのかわりにこのオブジェクトを使用します。 OutOfMemoryErrorが発生したためにExceptionInInitializerErrorの新しいインスタンスを作成できない場合は、次のステップでEのかわりにOutOfMemoryErrorオブジェクトを使用します。
LCを取得し、Cにエラーのラベルを付け、すべての待機スレッドに通知し、LCを解放して、前のステップで決定した理由Eまたはその置換を使用して、このプロシージャを突然完了します。
Java Virtual Machineの実装では、ステップ1 (およびステップ4/5のリリース)のロック取得を、Javaメモリー・モデルの観点では、ロックが取得された場合でも、最適化の実行時に存在するすべてのhappens-before順序付け(JLS§17.4.5)が、クラスの初期化がすでに完了しているかどうかを判断できるようにすることで、この手順を最適化できます。
バインディングは、Javaプログラミング言語以外の言語で記述され、nativeメソッドを実装するファンクションが実行できるようにJava Virtual Machineに統合されるプロセスです。 このプロセスは従来はリンクと呼ばれていましたが、Java Virtual Machineによるクラスまたはインタフェースのリンクとの混同を避けるために、この仕様ではバインディングという用語が使用されています。
Java Virtual Machineはスレッドでコードを実行します(§2.5)。 スレッドは、デーモン以外のスレッド、デーモン・スレッドまたはシャットダウン・フックのいずれかです。
スレッドがデーモンのステータスを取得する方法、およびシャットダウン・フックの登録方法の詳細は、リーダーはThreadおよびRuntimeのAPI仕様を参照しています。
スレッドは、(i)そのrunメソッドが正常に完了した場合、または(ii)そのrunメソッドが突然完了し、関連する捕捉されない例外ハンドラ(§2.10)が正常にまたは突然完了した場合、終了します。 実行するコードが残っていないため、スレッドは実行を完了したため、現在のメソッドはありません(§2.5.1)。
次のいずれかの状況が発生すると、Java Virtual Machineは終了します。
スレッドがSystem.exitまたはRuntime.exitを起動し、その結果Java Virtual Machineによって起動されたすべての停止フック(ある場合)が終了しました。
スレッドがRuntime.haltを呼び出しました。 (この状況では、シャットダウンフックが開始されません。)
Java Virtual Machineの実装では、外部イベントがJava Virtual Machineの終了を要求していると認識され、その結果、Java Virtual Machineによって起動されたすべての停止フック(ある場合)が終了しました。
イベントの性質は、この仕様の範囲外ですが、Java Virtual Machine実装が確実に処理できるものである必要があります。 例として、オペレーティング・システムからシグナルを受信します。
Java Virtual Machine実装が処理できない外部イベントが発生しました。 (この状況では、シャットダウンフックが開始されません。)
イベントの性質は、この仕様の範囲外ですが、Java Virtual Machine実装がなんらかの方法で認識またはリカバリできないものである必要があります。 たとえば、実装を実行しているプロセスで致命的なエラーが発生したり、実装を実行しているコンピュータから電源が切断されたりします。
Java Virtual Machineの終了時に、まだ終了していないデーモンまたは非デーモン・スレッドは、それ以上のJavaコードを実行しません。 スレッドの現在のメソッドは、正常にまたは突然完了しません。
シャットダウン・フックの実行中にRuntime.haltを呼び出したスレッドが原因でJava Virtual Machineが終了した場合、デーモンおよびデーモン以外のスレッドに加えて、まだ終了していないシャットダウン・フックはそれ以上のJavaコードを実行しません。
ネイティブ・アプリケーションは、JNI起動APIを使用して、初期クラス(JLS§12.1)のmainメソッドで実行を開始したJavaプログラムが、その非デーモン・スレッドがすべて終了したときに終了するように(JLS§12.8)、Java Virtual Machineを作成および破棄できます。 最後の非デーモン・スレッドが終了しても、Java Virtual Machineは自動的に終了しません。