目次
この章では、プログラムの実行時に発生するアクティビティについて説明します。 これは、Java仮想マシンのライフ・サイクルと、プログラムを形成するクラス、インタフェースおよびオブジェクトのライフ・サイクルに基づいて構成されています。
Java Virtual Machineは、指定されたクラスまたはインタフェースをロードしてから、この指定されたクラスまたはインタフェースでmainメソッドを呼び出して起動します。 §12.1では、このメソッドの実行に関連するロード、リンクおよび初期化のステップの概要を、この章の概念の概要として説明します。 さらに、ロードの詳細(§12.2)、リンク(§12.3)、および初期化(§12.4)についても説明します。
この章では、新しいクラス・インスタンスの作成手順(§12.5)およびクラス・インスタンスの最終処理手順(§12.6)の仕様を続けます。 これは、クラスのアンロード(§12.7)と、プログラムの終了時に従う手順(§12.8)を記述することによって終了する。
Java Virtual Machineは、指定されたクラスまたはインタフェースのmainメソッドを呼び出して実行を開始します。 このメソッドに仮パラメータがある場合は、文字列の配列である単一の引数が渡されます。
Java Virtual Machineの起動の正確なセマンティクスについては、The Java Virtual Machine Specification、 Java SE 26 Editionの第5章を参照してください。 ここでは、Javaプログラミング言語の視点から、このプロセスの概要を示します。
Java Virtual Machineに初期クラスまたはインタフェースを指定する方法は、この仕様の範囲外ですが、コマンドラインを使用するホスト環境では、完全修飾名として一般的です。コマンドライン引数として指定される初期クラスまたはインタフェース、およびmainメソッドに引数として渡される配列のコンポーネントとして取得される文字列として使用される次のコマンドライン引数。 元のコンパイル・ユニットがコンパクト・コンパイル・ユニット(§7.3)の場合、通常、最初のクラスまたはインタフェースの名前を指定するために、コンパイル・ユニットを含むファイルの名前(任意の拡張子を差し引いたもの)が使用されます。
たとえば、UNIX実装では、コマンドラインに次のように入力します。
java Test reboot Bob Dot Enzo
通常、Java Virtual Machineを起動するには、クラスTestのmainメソッド(名前のないパッケージのクラス)を呼び出して、4つの文字列"reboot"、"Bob"、"Dot"および"Enzo"を含む引数配列を渡します。
一方、ファイルHelloWorld.javaに次のコンパクト・コンパイル・ユニットが含まれているとします。
void main() {
System.out.println("Hello, World!");
}
コンパイル後のコマンドラインは次のようになります。
java HelloWorld
通常、Java Virtual Machineを起動するには、暗黙的に宣言されたクラスのmainメソッド(§8.1.8)を呼び出して、次の出力を生成します。
Hello, World!
ここでは、初期クラスまたはインタフェースを実行するためにJava Virtual Machineが実行するステップの概要を説明します。これらのステップについては、後の項で詳しく説明します。
初期クラスまたはインタフェースのmainメソッドを実行しようとすると、ロードされていない(つまり、Java Virtual Machineに現在このクラスまたはインタフェースのバイナリ表現が含まれていない)ことが検出されます。 次に、Java仮想マシンがクラス・ローダーを使用して、このようなバイナリ表現を見つけようとします。 この処理に失敗した場合は、エラーがスローされます。 このロード・プロセスは、§12.2で詳しく説明されています。
クラスまたはインタフェースがロードされたら、mainメソッドを呼び出す前に初期化する必要があります。 初期クラスは、すべてのクラスおよびインタフェースと同様に、初期化する前にリンクする必要があります。 リンクには、検証、準備および(オプションの)解決が含まれます。 リンクについては、§12.3で詳しく説明されています。
検証では、初期クラスまたはインタフェースのロードされた表現が正しいシンボルテーブルで整形式であることを確認します。 検証では、初期クラスまたはインタフェースを実装するコードが、Javaプログラミング言語およびJava Virtual Machineのセマンティック要件に準拠していることもチェックされます。 検証中に問題が検出されると、エラーがスローされます。 検証については、§12.3.1で詳しく説明されています。
準備には、静的ストレージと、Java仮想マシンの実装によって内部的に使用されるデータ構造(メソッド表など)の割当てが含まれます。 準備については、§12.3.2で詳しく説明されています。
解決とは、最初のクラスまたはインタフェースからほかのクラスおよびインタフェースへのシンボリック参照をチェックするプロセスであり、前述のほかのクラスおよびインタフェースをロードして、参照が正しいことを確認するプロセスです。
解決の手順は、初期リンケージの時点ではオプションです。 実装では、再帰的に参照されるクラスおよびインタフェースからすべてのシンボリック参照を解決するポイントに至るまで、非常に早期にリンクされているクラスまたはインタフェースからシンボリック参照を解決できます。 (この解決により、これらのロードおよびリンクの手順でエラーが発生する可能性があります。) この実装の選択肢は、C言語の単純な実装で長年使用されてきた「静的」リンクに似ています。 (これらの実装では、コンパイルされたプログラムは、通常、プログラムで使用されるライブラリルーチンへの完全に解決されたリンクを含む、完全にリンクされたバージョンのプログラムを含む「a.out」ファイルとして表されます。 これらのライブラリ・ルーチンのコピーは、a.outファイルに含まれています。)
かわりに、実装では、シンボリック参照がアクティブに使用されている場合にのみ解決するように選択されます。この戦略のすべてのシンボリック参照に対する一貫した使用は、「最も遅い」解決になります。 この場合、初期クラスまたはインタフェースに別のクラスへのいくつかのシンボリック参照があった場合、これらの参照がプログラムの実行中に使用されなかった場合は、それらの参照が一度に1つずつ解決されるか、またはまったく解決されない可能性があります。
解決が実行される際の唯一の要件は、解決中に検出されたエラーが、プログラムのある時点でスローされることです。そこでは、エラーに関与するクラスまたはインタフェースへの直接的または間接的なリンクが必要なプログラムにより、アクションが実行されます。 前述の静的実装例の選択を使用すると、初期クラスまたはインタフェースで記述されたクラスまたはインタフェース、あるいはさらに再帰的に参照されたクラスおよびインタフェースがプログラムの実行前に、ロードおよびリンケージエラーが発生する可能性があります。 「最も遅い」解決を実装したシステムでは、不正なシンボリック参照がアクティブに使用されている場合にのみ、これらのエラーがスローされます。
解決プロセスは、§12.3.3で詳しく説明されています。
この段階では、Java Virtual Machineは依然として初期クラスまたはインタフェースのmainメソッドを実行しようとしています。 これは、クラスまたはインタフェースが初期化されている場合にのみ許可されます(§12.4.1)。
初期化は、任意のクラス変数イニシャライザおよび初期クラスまたはインタフェースの静的イニシャライザをテキスト順で実行することで構成されます。 ただし、初期化する前に、直接スーパークラスを初期化し、直接スーパークラスの直接スーパークラスを再帰的に初期化する必要があります。 最も単純なケースでは、初期クラスは暗黙的な直接スーパークラスとしてObjectを持ちます。クラスObjectがまだ初期化されていない場合は、初期クラスまたはインタフェースを初期化する前に初期化する必要があります。 クラスObjectにはスーパークラスがないため、再帰はここで終了します。
初期クラスにスーパークラスとして別のクラスSuperがある場合は、初期クラスの前にSuperを初期化する必要があります。 これには、Superのロード、検証および準備が必要です(まだ実行されていない場合)。また、実装によっては、Superなどのシンボリック参照を再帰的に解決する場合もあります。
したがって、初期化によって、ロード、リンク、および初期化エラーが発生する可能性があります。これには他のクラスやインタフェースに関連するエラーも含まれます。
初期化プロセスは、§12.4で詳しく説明されています。
mainメソッドの起動
最後に、初期クラスまたはインタフェース(他の結果ロード、リンクおよび初期化が発生した可能性がある間)の初期化が完了すると、初期クラスまたはインタフェースで宣言または継承されたmainメソッドが呼び出されます。
特定のクラスで宣言または継承されるmainメソッドは、次のいずれかの場合に候補のmainメソッドです。
宣言型がStringの配列、void結果、およびpublic、protectedまたはパッケージ・アクセスである単一の仮パラメータ(§8.4.1)があります。
仮パラメータ、void結果およびpublic、protectedまたはパッケージ・アクセスはありません。
候補のmainメソッドは、staticまたはインスタンス・メソッドのいずれかです。 オプションで、throws句(§8.4.6)を指定できます。 単一の仮パラメータの型は、同じ型を示すString[]またはString...として指定できます。
候補のmainメソッドの形式は、Java SE 25で大幅に拡張されました。 これより前は、public、staticで、単一の仮パラメータを持つ必要がありました。可能な唯一のバリエーションは、単一の仮パラメータの型に対してString[]とString...でした。
初期クラスまたはインタフェースがそのメンバー間で複数の候補mainメソッドをカウントする場合、コンパイル時エラーはありません。
クラスまたはインタフェースに候補のmainメソッドが存在することは、継承される可能性があるため、すぐにわかりません。 たとえば、インタフェースのデフォルト・メソッドはインスタンス・メソッド(§9.4)であるため、インタフェースを実装するクラスによって継承される場合、候補のmainメソッドになる可能性があります。
初期クラスまたはインタフェースの候補のmainメソッドは、次のルールを適用したかのように呼び出されます。
単一の仮パラメータを持つ候補のmainメソッドがある場合、このメソッドが呼び出されます。 このメソッドがstaticの場合は、引数配列(§12.1)を渡して直接呼び出されます。 候補のmainメソッドがインスタンス・メソッドの場合、最初に初期クラスのインスタンスを作成する必要があります。 このクラス・インスタンスは、型引数なし、実際の引数なし、クラス本体なし、およびClassOrInterfaceTypeToInstantiateが初期クラスの名前である、修飾されていないクラス・インスタンス作成式(§15.9)を評価した結果です。 候補のmainメソッドが、結果のインスタンスで呼び出され、引数配列が渡されます。
それ以外の場合は、仮パラメータのない候補のmainメソッドがある場合、このメソッドが呼び出されます。 このメソッドがstaticの場合は、直接呼び出されます。 候補のmainメソッドがインスタンス・メソッドの場合、最初に初期クラスのインスタンスを作成する必要があります。 このクラス・インスタンスは、型引数なし、実際の引数なし、クラス本体なし、およびClassOrInterfaceTypeToInstantiateが初期クラスの名前である、修飾されていないクラス・インスタンス作成式(§15.9)を評価した結果です。 候補のmainメソッドは、実際の引数なしで結果のインスタンスで呼び出されます。
クラスには、同じシグネチャ(§8.4.2)を持つstaticメソッドとインスタンス・メソッドの両方を含めることはできません。
起動する候補のmainメソッドがない場合、またはインスタンスのmainメソッドの起動時に初期クラスのインスタンスの作成に失敗した場合、実行は失敗します。この場合、実装の正確な動作はこの仕様の範囲外です。
ロードとは、クラスまたはインタフェースのバイナリ形式を特定の名前で検索するプロセスを指します。たとえば、そのバイナリ形式では、クラスまたはインタフェースを表すClassオブジェクト(§1.4)をJavaコンパイラによってソース・コードから以前に計算されたバイナリ表現を取得し、そのバイナリ形式から構築します。
ロードの正確なセマンティクスについては、The Java Virtual Machine Specification、 Java SE 26 Editionの第5章を参照してください。 ここでは、Javaプログラミング言語の視点から、このプロセスの概要を示します。
クラスまたはインタフェースのバイナリ表現は、通常、The Java Virtual Machine Specification、 Java SE 26 Editionの第4章で説明されているclassファイル形式ですが、§13.1で指定された要件を満たしていれば、他の表現も可能です。
ロード・プロセスは、クラスClassLoaderとそのサブクラスによって実装されます。 クラスClassLoaderのメソッドdefineClassは、classファイル形式(§1.4)でバイナリ表現からClassオブジェクトを構築するために使用できます。
ClassLoaderのサブクラスが異なると、異なるロード・ポリシーを実装できます。 特に、クラス・ローダーは、クラスおよびインタフェースのバイナリ表現をキャッシュしたり、予想される使用状況に基づいてそれらをプリフェッチしたり、関連するクラスのグループをまとめてロードしたりできます。 たとえば、クラス・ローダーによって古いバージョンがキャッシュされるため、新しくコンパイルされたバージョンのクラスが見つからない場合、これらのアクティビティは実行中のアプリケーションに対して完全に透過的でない可能性があります。 ただし、プリフェッチやグループ・ロードを行わずに発生した可能性があるプログラム内のポイントでのみロード・エラーを反映するのはクラス・ローダーの責任です。
クラスのロード中にエラーが発生した場合、LinkageErrorクラスの次のいずれかのサブクラスのインスタンスが、要求されたクラスまたはインタフェースを(直接的または間接的に)使用するプログラム内の任意の時点でスローされます。
ロードには新しいデータ構造の割当てが含まれるため、OutOfMemoryErrorで失敗する可能性があります。
正常に動作するクラス・ローダーは、次の特性を有しています。
同じ名前を指定すると、クラス・ローダーは常に同じClassオブジェクトを返す必要があります。
クラス・ローダーL1がクラスまたはインタフェースCのロードを別のローダーL2に委任する場合、Cの直接スーパークラス型またはCの直接スーパーインタフェース型またはCの直接スーパーインタフェース型によって名前が付けられたクラスまたはインタフェースDに対して、またはC内のフィールドの型、またはC内のメソッドまたはコンストラクタの仮パラメータの型、またはC内のメソッドの戻り型によって、L1およびL2はDに対して同じClassオブジェクトを返す必要があります。
悪意のあるクラス・ローダーは、これらの特性に違反している可能性があります。 ただし、Java仮想マシンがこれを防ぐため、型システムのセキュリティが損なわれることはありません。
これらの問題の詳細は、The Java Virtual Machine Specification、 Java SE 26 EditionおよびDynamic Class Loading in the Java Virtual MachineのSheng LiangおよびGilad BrachaによるProceedings of OOPSLA '98(ACM SIGPLAN Notices、 Volume 33、 Number 10、 October 1998、 pages 36-44)を参照してください。 Javaプログラミング言語の設計の基本原則は、ClassLoaderなどの機密システム・クラスの実装によっても、Javaプログラミング言語で記述されたコードによってランタイム・タイプ・システムを逆転できないことです。
リンクは、クラスまたはインタフェースのバイナリ形式を取得し、それをJava Virtual Machineの実行時状態に組み合せて実行できるようにするプロセスです。 クラスまたはインタフェースは、リンクされる前に常にロードされます。
リンクの正確なセマンティクスについては、The Java Virtual Machine Specification、 Java SE 26 Editionの第5章を参照してください。 ここでは、Javaプログラミング言語の視点から、このプロセスの概要を示します。
リンクには、検証、準備およびシンボリック参照の解決という、3つの異なるアクティビティが関与します。
この仕様により、リンク・アクティビティが(回帰、ロードにより)実行されるタイミングについて、柔軟な実装が可能になります。初期化の前にクラスまたはインタフェースが完全に検証および準備され、リンケージ中に検出されたエラーは、プログラム内で、そのエラーに関連するクラスまたはインタフェースへのリンケージを必要とする可能性のあるプログラムによってなんらかのアクションが実行されるポイントでスローされるという、Javaプログラミング言語のセマンティクスが満たされていることが前提です。
たとえば、実装で、使用時にのみ個別にクラスまたはインタフェースの各シンボリック参照を解決するように選択することも(レイジーまたは遅延解決)、クラスの検証中に一度にそれらすべてを解決するように選択することもできます(静的解決)。 これは、一部の実装で、クラスまたはインタフェースが初期化された後も解決プロセスが続く場合があることを意味します。
リンクには新しいデータ構造の割当てが含まれるため、OutOfMemoryErrorで失敗する可能性があります。
検証により、クラスまたはインタフェースのバイナリ表現が構造的に正しいことが保証されます。 たとえば、すべての命令に有効な操作コードがあること、すべての分岐命令が命令の途中ではなく、ほかの命令の先頭に分岐すること、すべてのメソッドに構造的に正しい署名が提供されること、およびすべての命令が Java Virtual Machineの型規則に従うことを検査します。
検証中にエラーが発生すると、次のクラスLinkageErrorのサブクラスのインスタンスが、そのクラスが検証される原因となったプログラム内のポイントでスローされます。
準備では、クラスまたはインタフェースのstaticフィールド(クラス変数および定数)を作成し、それらのフィールドをデフォルト値に初期化します(§4.12.5)。 この場合、ソース・コードの実行は必要ありません。静的フィールドの明示的なイニシャライザは、準備ではなく初期化(§12.4)の一部として実行されます。
Java Virtual Machineの実装では、準備時に追加のデータ構造を事前計算して、後でクラスまたはインタフェースの操作をより効率的にすることができます。 特に有用なデータ構造の1つは、「メソッド表」または他のデータ構造で、呼出し時にスーパークラスを検索することなく、クラスのインスタンスでメソッドを呼び出すことができます。
クラスまたはインタフェースのバイナリ表現は、他のクラスおよびインタフェースとそのフィールド、メソッドおよびコンストラクタを、他のクラスおよびインタフェースのバイナリ名(§13.1)を使用して象徴的に参照します。 フィールドおよびメソッドの場合、これらのシンボリック参照には、フィールドまたはメソッドがメンバーであるクラスまたはインタフェースの名前と、フィールドまたはメソッド自体の名前、および適切な型情報が含まれます。
シンボリック参照を使用するには事前に解決を実施する必要があり、そのときにシンボリック参照が正しいことが確認され、通常は、参照が繰り返し使用される場合にさらに効率的に処理できる直接参照に置換されます。
解決中にエラーが発生すると、エラーがスローされます。 通常、これはクラスIncompatibleClassChangeErrorの次のサブクラスの1つのインスタンスになりますが、IncompatibleClassChangeErrorの他のサブクラスのインスタンスでも、クラスIncompatibleClassChangeError自体のインスタンスでもかまいません。 このエラーは、シンボリック参照を使用するプログラム内の任意の時点で、直接的または間接的にスローされる可能性があります。
IllegalAccessError: フィールドの使用または割当て、メソッドの呼出し、または参照を含むコードが持たないクラスのインスタンスの作成を指定するシンボリック参照が検出されました。フィールドまたはメソッドがprivate、protectedまたはパッケージ・アクセス(publicではない)で宣言されたため、または参照を含むコードにエクスポートまたはオープンされたパッケージでクラスがpublicとして宣言されなかったため、アクセス可能です。
これは、たとえば、最初にpublicと宣言されたフィールドが、そのフィールドを参照する別のクラスがコンパイルされた後にprivateに変更された場合(§13.4.7)、またはpublicクラスが宣言されたパッケージが、そのクラスを参照する別のモジュールがコンパイルされた後にそのモジュールによってエクスポートされなくなった場合(§13.3)に発生します。
InstantiationError: クラス・インスタンス作成式で使用されるシンボリック参照が検出されましたが、その参照がインタフェースまたは抽象クラスを参照することが判明したため、インスタンスを作成できません。
これは、たとえば、当該クラスを参照する別のクラスがコンパイルされた後(§13.4.1)、もともとabstractではないクラスがabstractに変更された場合などに起こります。
NoSuchFieldError: 特定のクラスまたはインタフェースの特定のフィールドを参照するシンボリック参照が検出されましたが、クラスまたはインタフェースにはその名前のフィールドが含まれていません。
これは、たとえば、フィールドを参照する別のクラスがコンパイルされた後に、フィールド宣言がクラスから削除された場合に起こることがあります(§13.4.8)。
NoSuchMethodError: 特定のクラスまたはインタフェースの特定のメソッドを参照するシンボリック参照が検出されましたが、そのクラスまたはインタフェースにはそのシグネチャのメソッドが含まれていません。
これは、たとえば、メソッドを参照する別のクラスがコンパイルされた後に、メソッド宣言がクラスから削除された場合に発生する可能性があります(§13.4.12)。
また、LinkageErrorのサブクラスであるUnsatisfiedLinkErrorは、実装が見つからないnativeメソッドをクラスが宣言している場合にスローされます。 このエラーは、Java Virtual Machine (§12.3)の実装によってどの種類の解決戦略が使用されているかに応じて、メソッドが使用された場合、またはそれ以前の場合に発生します。
クラスの初期化は、その静的イニシャライザと、クラスで宣言されたstaticフィールド(クラス変数)のイニシャライザの実行で構成されます。
インタフェースの初期化は、インタフェースで宣言されたフィールド(定数)の初期化子の実行で構成されます。
クラスまたはインタフェースTは、次のいずれかが最初に出現する直前に初期化されます。
Tはクラスで、Tのインスタンスが作成されます。
Tによって宣言されたstaticメソッドが呼び出されます。
Tで宣言されたstaticフィールドが割り当てられます。
Tで宣言されたstaticフィールドが使用され、このフィールドは定数変数ではありません(§4.12.4)。
クラスが初期化されると、そのスーパークラスは初期化されます(以前に初期化されていない場合)。また、デフォルト・メソッド(§9.4.3)を宣言するスーパーインタフェース(§8.1.5)も初期化されます(まだ初期化されていない場合)。 インタフェースを初期化しても、そのスーパーインタフェースは初期化されません。
staticフィールドへの参照(§8.3.1.1)は、実際に宣言するクラスまたはインタフェースのみを初期化します。ただし、サブクラス、サブインタフェースまたはインタフェースを実装するクラスの名前を介して参照できます。
クラスClassおよびパッケージjava.lang.reflectで特定のリフレクティブ・メソッドを呼び出すと、クラスまたはインタフェースの初期化も行われます。
クラスまたはインタフェースは、その他の状況では初期化されません。
コンパイラは、インタフェース内に合成デフォルト・メソッド(明示的にも暗黙的にも宣言されていないデフォルト・メソッド)を生成できることに注意してください(§13.1)。 このようなメソッドは、インタフェースを初期化する必要があることを示さないソース・コードにもかかわらず、インタフェースの初期化をトリガーします。
目的は、クラスまたはインタフェースに一貫性のある状態にする初期化子のセットがあり、この状態が他のクラスによって観測される最初の状態になることです。 staticイニシャライザおよびクラス変数イニシャライザは、テキストの順序で実行され、これらのクラス変数がスコープ(§8.3.3)内にある場合でも、使用後にテキストで宣言されるクラス内で宣言されたクラス変数を参照することはできません。 この制限は、コンパイル時にほとんどの循環または不正な形式の初期化を検出するように設計されています。
初期化コードが無制限であるという事実により、初期化式が評価される前に、クラス変数の値が初期デフォルト値のままである場合にクラス変数の値を観察できる例を構築できますが、このような例は実際にはまれです。 (このような例は、インスタンス変数の初期化のためにも構成できます(§12.5)。) Javaプログラミング言語のフルパワーは、これらのイニシャライザで使用できます。プログラマは慎重に行う必要があります。 この機能はコード・ジェネレータに余分な負担をかけますが、Javaプログラミング言語が同時であるため、この負担はどのような場合でも発生します(§12.4.2)。
例12.4.1-1 サブクラスの前にスーパークラスが初期化される
class Super {
static { System.out.print("Super "); }
}
class One {
static { System.out.print("One "); }
}
class Two extends Super {
static { System.out.print("Two "); }
}
class Test {
public static void main(String[] args) {
One o = null;
Two t = new Two();
System.out.println((Object)o == (Object)t);
}
}
このプログラムは出力を生成します:
Super Two false
クラスOneはアクティブに使用されないため、リンクされないため、初期化されません。 クラスTwoは、そのスーパークラスSuperが初期化された後にのみ初期化されます。
例12.4.1-2 staticフィールドを宣言するクラスのみが初期化されます。
class Super {
static int taxi = 1729;
}
class Sub extends Super {
static { System.out.print("Sub "); }
}
class Test {
public static void main(String[] args) {
System.out.println(Sub.taxi);
}
}
このプログラムでは、次のもののみが出力されます。
1729
クラスSubは初期化されないため、Sub.taxiへの参照はクラスSuperで実際に宣言されたフィールドへの参照であり、クラスSubの初期化はトリガーされません。
例12.4.1-3. インタフェースの初期化でスーパーインタフェースが初期化されない
interface I {
int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
int k = Test.out("k", 5);
}
class Test {
public static void main(String[] args) {
System.out.println(J.i);
System.out.println(K.j);
}
static int out(String s, int i) {
System.out.println(s + "=" + i);
return i;
}
}
このプログラムは出力を生成します:
1 j=3 jj=4 3
J.iへの参照は、定数変数であるフィールド(§4.12.4)に対するものです。したがって、Iが初期化されることはありません(§13.4.9)。
K.jへの参照は、定数変数ではないインタフェースJで実際に宣言されたフィールドへの参照です。これにより、インタフェースJのフィールドが初期化されますが、そのスーパーインタフェースIのフィールドやインタフェースKのフィールドは初期化されません。
Kという名前がインタフェースJのフィールドjを参照するために使用されているにもかかわらず、インタフェースKは初期化されません。
Javaプログラミング言語はマルチスレッドであるため、クラスまたはインタフェースの初期化には慎重な同期が必要です。これは、他のスレッドが同時に同じクラスまたはインタフェースを初期化しようとしている可能性があるためです。 また、クラスまたはインタフェースの初期化は、そのクラスまたはインタフェースの初期化の一部として再帰的に要求される可能性もあります。たとえば、クラス Aの変数イニシャライザは、無関係クラス Bのメソッドを呼び出します。このメソッドは、次にクラス Aのメソッドを呼び出します。 Java Virtual Machineの実装では、次の手順を使用して、同期および再帰的な初期化を処理します。
このプロシージャは、Classオブジェクトがすでに検証および準備されており、Classオブジェクトに次の4つの状況のいずれかを示す状態が含まれていることを前提としています。
このClassオブジェクトは検証および準備されますが、初期化されません。
このClassオブジェクトは、特定のスレッドTによって初期化されています。
このClassオブジェクトは完全に初期化され、すぐに使用できます。
このClassオブジェクトは、初期化が試行されて失敗したため、間違った状態にあります。
クラスまたはインタフェースCごとに、一意の初期化ロックLCがあります。 CからLCへのマッピングは、Java Virtual Machine実装の判断に任されます。 Cを初期化する手順は次のとおりです。
Cの初期化ロックLCで同期します。 これには、現在のスレッドがLCを取得できるまで待機することが含まれます。
CのClassオブジェクトが、他のスレッドによってCの初期化が進行中であることを示す場合は、LCを解放し、進行中の初期化が完了したことを通知するまで現在のスレッドをブロックし、その時点でこのステップを繰り返します。
CのClassオブジェクトが、現在のスレッドによってCの初期化が進行中であることを示す場合、これは初期化の再帰的リクエストである必要があります。 LCを解放し、正常に完了します。
CのClassオブジェクトがCがすでに初期化されていることを示している場合、これ以上のアクションは必要ありません。 LCを解放し、正常に完了します。
CのClassオブジェクトが誤った状態の場合、初期化はできません。 LCを解放し、NoClassDefFoundErrorをスローします。
それ以外の場合は、CのClassオブジェクトの初期化が現在のスレッドによって進行中であるという事実を記録し、LCをリリースします。
次に、Cがインタフェースではなくクラスである場合は、SCをそのスーパークラスにし、SI1、...、SInを、少なくとも1つのデフォルトメソッドを宣言する Cのすべてのスーパーインタフェースにするようにします。 スーパーインタフェースの順序は、Cによって直接実装される各インタフェースのスーパーインタフェース階層に対する再帰的列挙によって指定されます(Cのimplements句の左から右の順序)。 Cによって直接実装された各インタフェース Iについて、Iを返す前に、Iのスーパーインタフェース(Iの extends句の左から右の順序)で列挙が再発します。
リスト[ SC、 SI1、 ...、 SIn ]の各 Sについて、Sがまだ初期化されていない場合は、この手順全体を再帰的に Sに対して実行します。 必要に応じて、最初に Sを検証して準備します。
例外がスローされたためにSの初期化が突然完了した場合、LCを取得し、CのClassオブジェクトにエラーのラベルを付け、すべての待機スレッドに通知し、LCを解放して、突然完了し、Sの初期化の結果と同じ例外をスローします。
次に、定義クラス・ローダーを問い合せて、アサーションがCに対して有効(§14.10)であるかどうかを判断します。
次に、クラスのクラス変数イニシャライザと静的イニシャライザ、またはインタフェースのフィールド・イニシャライザのいずれかを、単一ブロックであるかのようにテキスト順で実行します。
イニシャライザの実行が正常に完了した場合は、LCを取得し、CのClassオブジェクトに完全初期化済のラベルを付け、すべての待機スレッドに通知し、LCを解放して、このプロシージャを通常どおりに完了します。
それ以外の場合、イニシャライザは例外 Eをスローして突然完了している必要があります。 EのクラスがErrorまたはそのサブクラスの1つでない場合は、引数としてEを使用してクラスExceptionInInitializerErrorの新しいインスタンスを作成し、次のステップでEのかわりにこのオブジェクトを使用します。 OutOfMemoryErrorが発生したためにExceptionInInitializerErrorの新しいインスタンスを作成できない場合は、次のステップでEのかわりにOutOfMemoryErrorオブジェクトを使用します。
LCを取得し、CのClassオブジェクトにエラーのラベルを付け、すべての待機スレッドに通知し、LCを解放して、前のステップで決定した理由Eまたはその置換を使用して、このプロシージャを突然完了します。
実装では、ステップ1 (およびステップ4/5のリリース)のロック取得を消去することで、クラスの初期化がすでに完了していることを確認できます。ただし、メモリー・モデルの観点では、ロックが取得された場合に存在し、最適化の実行時にまだ存在する前の順序がすべて存在する場合です。
コード・ジェネレータでは、クラスまたはインタフェースの初期化可能なポイントを保持し、前述した初期化プロシージャの呼出しを挿入する必要があります。 この初期化プロシージャが正常に完了し、Classオブジェクトが完全に初期化され、使用する準備ができている場合、初期化プロシージャの起動が不要になり、コードから除外されることがあります。たとえば、このプロシージャにパッチを適用するか、コードを再生成します。
コンパイル時分析では、関連するクラスおよびインタフェースのグループの初期化順序を決定できる場合に、生成されたコードからクラスまたはインタフェースが初期化されているという多くのチェックを排除できる場合があります。 ただし、このような分析では、同時実行性および初期化コードが制限されていないことを完全に考慮する必要があります。
クラス・インスタンス作成式(§15.9)の評価によってクラスがインスタンス化されると、新しいクラス・インスタンスが明示的に作成されます。
次の状況では、新しいクラス・インスタンスが暗黙的に作成される場合があります。
文字列リテラル(§3.10.5)またはテキスト・ブロック(§3.10.6)を含むクラスまたはインタフェースをロードすると、文字列リテラルまたはテキスト・ブロックで表される文字列を示す新しいStringオブジェクトが作成される場合があります。 (このオブジェクトの作成は、文字列リテラルまたはテキスト・ブロックで表される文字列と同じUnicodeコード・ポイントのシーケンスを示すStringのインスタンスが以前にインターンされている場合には発生しません。)
ボクシング変換を引き起こす操作の実行(§5.1.7)。 ボクシング変換では、プリミティブ型の1つに関連付けられたラッパー・クラスの新規オブジェクト(Boolean、Byte、Short、Character、Integer、Long、Float、Double)を作成できます。
定数式(§15.29)の一部ではない文字列連結演算子+(§15.18.1)を実行すると、結果を表す新しいStringオブジェクトが常に作成されます。 文字列連結演算子では、プリミティブ型の値に対して一時ラッパー・オブジェクトを作成することもできます。
メソッド参照式(§15.13.3)またはラムダ式(§15.27.4)の評価では、関数型(§9.8)を実装するクラスの新しいインスタンスを作成する必要がある場合があります。
これらの各状況では、クラス・インスタンス作成プロセスの一部として、指定された引数(おそらく何もない)でコールされる特定のコンストラクタ(§8.8)が識別されます。
新しいクラス・インスタンスが作成されるたびに、クラスで宣言されたすべてのインスタンス変数およびクラスの各スーパークラスで宣言されたすべてのインスタンス変数(非表示にできるすべてのインスタンス変数を含む)(§8.3)の領域とともにメモリー領域が割り当てられます。
オブジェクトにメモリーを割り当てるための十分な領域がない場合、クラス・インスタンスの作成はOutOfMemoryErrorで突然完了します。 それ以外の場合、新しいオブジェクト内のすべてのインスタンス変数(スーパークラスで宣言されたものを含む)は、デフォルト値(§4.12.5)に初期化されます。
新しく作成されたオブジェクトへの参照が結果として返される直前に、指定されたコンストラクタが処理され、次の手順に従って新しいオブジェクトが初期化されます。
コンストラクタの引数を、このコンストラクタ呼出しのために新しく作成されたパラメータ変数に割り当てます。
このコンストラクタに明示的なコンストラクタ呼出し(§8.8.7.1)が含まれていない場合は、ステップ5から続行します。
コンストラクタ本体のプロローグがあれば、BlockStatementsを実行します。 文の実行が突然完了した場合は、同じ理由でコンストラクタの実行が突然完了します。そうでない場合は、次のステップに進みます。
明示的なコンストラクタの呼出しは、同じクラス内の別のコンストラクタの呼出し(thisを使用)またはスーパークラス・コンストラクタの呼出し(superを使用)のいずれかです。 コンストラクタ呼出しの引数を評価し、同じ7つのステップを使用してコンストラクタ呼出しを再帰的に処理します。 コンストラクタ呼出しが突然完了した場合は、同じ理由でこの手順が突然完了します。 そうでない場合は、呼出しが同じクラス内の別のコンストラクタの呼出しであればステップ7から続行し、スーパークラス・コンストラクタの呼出しであればステップ6から続行します。
このコンストラクタがObject以外のクラス用である場合、このコンストラクタには、引数を指定しないスーパークラス・コンストラクタの暗黙的な呼出しが含まれます。 この場合は、これらの同じ7つのステップを使用して暗黙的コンストラクタ呼出しを再帰的に処理します。 コンストラクタ呼出しが突然完了した場合は、同じ理由この手順が突然完了します。そうでない場合は、次のステップに進みます。
このクラスのインスタンス・イニシャライザおよびインスタンス変数イニシャライザを実行し、インスタンス変数イニシャライザの値を対応するインスタンス変数に左から右へ順に割り当て、クラスのソース・コードにテキストで表示されるようにします。 これらのイニシャライザのいずれかの実行で例外が発生した場合は、それ以降のイニシャライザは処理されず、同じ例外でこの手順が突然完了します。そうでない場合は、次のステップに進みます。
このコンストラクタのエピローグのBlockStatements(存在する場合)を実行します。 文の実行が突然完了した場合は、同じ理由でこの手順が突然完了します。 それ以外の場合、この手順は正常に完了します。
C++とは異なり、Javaプログラミング言語では、新しいクラス・インスタンスの作成中にメソッド・ディスパッチに対するルール変更は指定されません。 オブジェクトの初期化中、サブクラスでオーバーライドされるメソッドが呼び出された場合、新しいオブジェクトが完全に初期化される前でも、これらのオーバーライド・メソッドが使用されます。 クラスは、コンストラクタ本体のプロローグでフィールドに代入することで、初期化されていない状態の不要な露出を回避できます。
例12.5-1. インスタンス作成の評価
class Point {
int x, y;
Point() { x = 1; y = 1; }
}
class ColoredPoint extends Point {
int color = 0xFF00FF;
}
class Test {
public static void main(String[] args) {
ColoredPoint cp = new ColoredPoint();
System.out.println(cp.color);
}
}
ここでは、ColoredPointの新しいインスタンスが作成されます。 まず、フィールドx、yおよびcolorを保持するために、新しいColoredPointに領域が割り当てられます。 これらのフィールドはすべてデフォルト値に初期化されます(この場合、各フィールドの0)。 次に、引数を指定しないColoredPointコンストラクタが最初に呼び出されます。 ColoredPointはコンストラクタを宣言しないため、次の形式のデフォルトのコンストラクタが暗黙的に宣言されます。
ColoredPoint() { super(); }
このコンストラクタは、引数を指定せずにPointコンストラクタを呼び出します。 Pointコンストラクタはコンストラクタの呼出しで始まらないため、Javaコンパイラは、次のように記述されたかのように、スーパークラス・コンストラクタの暗黙的な呼出しを引数なしで提供します。
Point() { super(); x = 1; y = 1; }
したがって、引数をとらないObjectのコンストラクタが呼び出されます。
クラスObjectにはスーパークラスがないため、再帰はここで終了します。 次に、Objectのインスタンス・イニシャライザおよびインスタンス変数イニシャライザが呼び出されます。 次に、引数をとらないObjectのコンストラクタの本体が実行されます。 このようなコンストラクタはObjectで宣言されないため、Javaコンパイラはデフォルトのコンストラクタを提供します。この特別な場合は、次のようになります。
Object() { }
このコンストラクタの実行は効果がないまま返されます。
次に、クラスPointのインスタンス変数のすべての初期化子が実行されます。 この場合、xおよびyの宣言では初期化式が提供されないため、この例のステップではアクションは必要ありません。 次に、Pointコンストラクタの本体が実行され、xが1に、yが1に設定されます。
次に、クラスColoredPointのインスタンス変数のイニシャライザが実行されます。 このステップでは、値0xFF00FFがcolorに割り当てられます。 最後に、ColoredPointコンストラクタのエピローグ(superの呼出し後の部分)が実行されます。エピローグには文がないため、これ以上のアクションは不要で、初期化が完了します。
例12.5-2. インスタンス作成時の動的ディスパッチ
class Super {
Super() { printThree(); }
void printThree() { System.out.println("three"); }
}
class Test extends Super {
int three = (int)Math.PI; // That is, 3
void printThree() { System.out.println(three); }
public static void main(String[] args) {
Test t = new Test();
t.printThree();
}
}
このプログラムは出力を生成します:
0 3
これは、クラスSuperのコンストラクタでのprintThreeの起動によって、クラスSuperのprintThreeの定義が呼び出されるのではなく、クラスTestのprintThreeのオーバーライド定義が呼び出されることを示しています。 したがって、このメソッドは、Testのフィールド・イニシャライザが実行される前に実行されます。そのため、最初の値出力は0で、Testのフィールドthreeが初期化されるデフォルト値です。 後でメソッドmainでprintThreeを呼び出すと、同じprintThree定義が呼び出されますが、その時点までにインスタンス変数threeのイニシャライザが実行されたため、値3が出力されます。
例12.5-3. プロローグでのフィールドの初期化
class Super {
Super() { printThree(); }
void printThree() { System.out.println("three"); }
}
class Test extends Super {
int three;
public Test() {
three = (int)Math.PI; // That is, 3
super();
}
void printThree() { System.out.println(three); }
public static void main(String[] args) {
Test t = new Test();
t.printThree();
}
}
この例12.5-2の代替は、次の出力を生成します:
3 3
フィールドthreeはTestコンストラクタのプロローグで初期化されるため、その割当ては、ステップ4のSuperコンストラクタを実行する前に、オブジェクト初期化プロシージャのステップ3で行われます。 この方法でthreeを初期化すると、デフォルト値0で監視できなくなります。
Java SE 26では、Java SEプラットフォーム仕様により、Java SEプラットフォームの将来のリリースからファイナライズが削除されることを想定して、Java SEプラットフォームの実装でクラス・インスタンスのファイナライズを無効にできます。
クラスObjectには、finalizeというprotectedメソッドがあります。このメソッドは、他のクラスによってオーバーライドできます。 オブジェクトに対して起動できるfinalizeの特定の定義は、そのオブジェクトのファイナライザと呼ばれます。 オブジェクトの記憶域がガベージ・コレクタによって再利用される前に、Java Virtual Machineはそのオブジェクトのファイナライザを呼び出します。
ファイナライザは、自動ストレージマネージャーによって自動的に解放できないリソースを解放する機会を提供します。 このような状況では、オブジェクトによって使用されるメモリーを再利用するだけでは、保持されていたリソースが再利用されることは保証されません。
Javaプログラミング言語では、ファイナライザがどのくらい早く呼び出されるかは指定しません。ただし、オブジェクトの記憶域が再使用される前にファイナライザが呼び出されます。
Javaプログラミング言語では、特定のオブジェクトに対してファイナライザを起動するスレッドは指定されません。
多くのファイナライザ・スレッドがアクティブである可能性があり(大規模な共有メモリー・マルチプロセッサで必要になる場合もあります)、また、大規模な接続済データ構造がガベージになった場合、そのデータ構造内のすべてのオブジェクトに対するfinalizeメソッドを同時に起動でき、各ファイナライザ呼出しは異なるスレッドで実行されることに注意してください。
Javaプログラミング言語では、finalizeメソッド・コールの順序付けは行われません。 ファイナライザは、任意の順序で、または同時に呼び出すことができます。
たとえば、ファイナライズされていないオブジェクトの循環リンク・グループが到達不能(またはファイナライザ・リーチ可能)になった場合、すべてのオブジェクトがまとめてファイナライズ可能になることがあります。 最終的に、これらのオブジェクトのファイナライザは、任意の順序で、または同時に複数のスレッドを使用して呼び出すことができます。 自動ストレージ・マネージャが後でオブジェクトにアクセスできないことを検出した場合は、そのストレージを再利用できます。
すべてのオブジェクトにアクセスできなくなった場合に、オブジェクト・セットに対してファイナライザのようなメソッドのセットを指定された順序で呼び出すクラスを実装するのは簡単です。 このようなクラスの定義は、読者の演習として残されます。
ファイナライザを起動するスレッドは、ファイナライザの起動時にユーザーが表示できる同期ロックを保持しないことが保証されます。
ファイナライズ中に捕捉されない例外がスローされた場合、その例外は無視され、そのオブジェクトのファイナライズは終了します。
オブジェクトのコンストラクタの完了は、(§17.4.5)そのfinalizeメソッドの実行前(事前発生の正式な意味での)に発生します。
クラスObjectで宣言されたfinalizeメソッドは、アクションを実行しません。 Objectクラスがfinalizeメソッドを宣言するということは、任意のクラスのfinalizeメソッドが常にそのスーパークラスのfinalizeメソッドを呼び出すことができることを意味します。 これは、スーパークラス内のファイナライザのアクションを無効化するプログラマの意図でないかぎり、常に実行する必要があります。 (コンストラクタとは異なり、ファイナライザはスーパークラスのファイナライザを自動的に起動しません。このような呼出しは明示的にコーディングする必要があります。)
効率化のために、実装では、クラスObjectのfinalizeメソッドをオーバーライドしないクラスを追跡したり、わずかな方法でオーバーライドしたりできます。
たとえば:
protected void finalize() throws Throwable {
super.finalize();
}
実装は、§12.6.1で説明されているように、オーバーライドされないファイナライザを持つようなオブジェクトを扱い、より効率的にファイナライズすることを推奨します。
ファイナライザは、他のメソッドと同様に明示的に呼び出すことができます。
パッケージjava.lang.refは、ガベージ・コレクションおよびファイナライズと相互作用する弱い参照を示します。 Javaプログラミング言語との特別な相互作用を持つAPIと同様に、実装者はjava.lang.ref APIによって課される要件を認識している必要があります。 この仕様では、弱い参照については一切説明しません。 詳細は、APIドキュメントを参照してください。
すべてのオブジェクトは、到達可能、ファイナライザ到達可能または到達不可の2つの属性によって特徴付けられます。また、未ファイナライズ、ファイナライズ可能またはファイナライズ済の場合もあります。
到達可能オブジェクトは、任意のライブ・スレッドからの潜在的な継続計算でアクセスできる任意のオブジェクトです。
finalizer-reachableオブジェクトは、なんらかの参照チェーンを介してファイナライズ可能オブジェクトからアクセスできますが、ライブ・スレッドからはアクセスできません。
到達不能オブジェクトは、いずれの方法でも到達できません。
ファイナライズされていないオブジェクトでは、ファイナライザが自動的に呼び出されることはありません。
finalizedオブジェクトは、ファイナライザを自動的に呼び出しました。
ファイナライズ可能オブジェクトではファイナライザが自動的に呼び出されることはありませんが、Java Virtual Machineでは最終的にファイナライザが自動的に呼び出される場合があります。
オブジェクトoは、コンストラクタがoでObjectのコンストラクタを起動し、呼出しが正常に完了するまで(つまり、例外をスローせずに)ファイナライズできません。 オブジェクトのフィールドへのすべての事前ファイナライズ書込みは、そのオブジェクトのファイナライズから参照できる必要があります。 さらに、そのオブジェクトのフィールドの事前ファイナライズ読取りでは、そのオブジェクトのファイナライズが開始された後に発生する書込みは表示されない場合があります。
プログラムの変換を最適化することで、到達可能なオブジェクトの数が、到達可能とみなされるオブジェクトの数より少なくなるように設計できます。 たとえば、Javaコンパイラまたはコード・ジェネレータでは、nullに使用されなくなった変数またはパラメータを設定して、そのようなオブジェクトの記憶域をより早く再利用可能にすることができます。
この別の例は、オブジェクトのフィールドの値がレジスタに格納される場合に発生します。 プログラムは、オブジェクトではなくレジスタにアクセスし、オブジェクトに再度アクセスすることはありません。 これは、オブジェクトがゴミであることを意味します。 この種の最適化が許可されるのは、参照がスタック上にある場合のみであり、ヒープには格納されないことに注意してください。
たとえば、ファイナライザ・ガーディアン・パターンを考えてみます:
class Foo {
private final Object finalizerGuardian = new Object() {
protected void finalize() throws Throwable {
/* finalize outer Foo object */
}
}
}
ファイナライザ・ガーディアンは、サブクラスがfinalizeをオーバーライドし、super.finalizeを明示的にコールしない場合、super.finalizeを強制的にコールします。
これらの最適化がヒープに格納されている参照に対して許可されている場合、Javaコンパイラは、finalizerGuardianフィールドが読み取られないこと、フィールドをnullにすること、オブジェクトをすぐに収集すること、およびファイナライザを早期に呼び出すことを検出できます。 これは、意図に反して実行されます。プログラマは、FooインスタンスにアクセスできなくなったときにFooファイナライザをコールしたいと考えていました。 したがって、この種の変換は有効ではありません。内部クラス・オブジェクトは、外部クラス・オブジェクトが到達可能であるかぎり、到達可能である必要があります。
このソートを変換すると、finalizeメソッドの呼出しが、想定よりも早く発生する可能性があります。 これを防ぐために、同期によってオブジェクトが存続している可能性があるという考えが強制されます。 オブジェクトのファイナライザによってそのオブジェクトで同期が発生する可能性がある場合、そのオブジェクトは、ロックが保持されるたびにそのオブジェクトが存続し、到達可能とみなされる必要があります。
これによって同期の排除が妨げられるわけではないことに注意してください。同期では、ファイナライザが同期する可能性がある場合にのみオブジェクトが動作し続けます。 ファイナライザは別のスレッドで発生するため、多くの場合、同期を削除できませんでした。
メモリー・モデル(§17.4)は、ファイナライザで実行されるアクションをコミットできるタイミングを決定できる必要があります。 この項では、ファイナライズとメモリー・モデルの相互作用について説明します。
各実行には、diというラベルの付いた到達可能性決定ポイントが多数あります。 各アクションは、come-before diまたはcomes-after diのいずれかです。 明示的に記載されている場合を除き、この項で説明する事前注文は、メモリー・モデル内の他のすべての順序付けとは無関係です。
rが、書込みwおよびrがdiの前に来る読取りの場合は、wがdiの前に来る必要があります。
xおよびyが同じ変数またはモニターに対する同期アクションであり、so(x、 y) (§17.4.4)およびyがdiより前である場合は、xがdiより前である必要があります。
到達可能性決定ポイントごとに、一部のオブジェクト・セットは到達不可としてマークされ、それらのオブジェクトの一部のサブセットは最終可能としてマークされます。 これらの到達可能性決定ポイントは、java.lang.refパッケージのAPIドキュメントに記載されているルールに従って、参照がチェック、エンキューおよびクリアされるポイントでもあります。
diポイントで確実に到達可能とみなされるオブジェクトは、次のルールの適用によって到達可能であることが示されるオブジェクトのみです。
w1によって書き込まれる値がBへの参照となるように、クラスCのstaticフィールドvへの書込みw1が存在する場合、オブジェクトBはstaticフィールドからdiで確実に到達できます。クラスCは到達可能なクラス・ローダーによってロードされ、hb(w2、 w1)がtrueではなく、w1とw2の両方がdiの前にあるように、vへの書込みw2は存在しません。
w1によって書き込まれた値がBへの参照であり、hb(w2、 w1)がtrueではなく、w1によって書き込まれた値がw2からhb(w2、 w1)がtrueでなく、w1とw2の両方がdiの前にあるように、vへの書込みw2が存在しない場合、オブジェクトBは必ずAからアクセス可能です。
オブジェクトCがオブジェクトBから確実に到達可能であり、オブジェクトBがオブジェクトAから確実に到達可能である場合、CはAから確実に到達可能です。
オブジェクトXがdiでアクセス不可としてマークされている場合、次のようになります。
Xは、staticフィールドからdiで確実にアクセスできないようにする必要があります。
diの後に続くスレッドt内のXのすべてのアクティブな使用は、Xのファイナライザ呼出し、またはXへの参照のdiの後に続く読取りを実行するスレッドtの結果として発生する必要があります。
Xへの参照を参照するdi以降のすべての読取りでは、diでアクセスできないオブジェクトの要素への書込みを参照するか、di以降の書込みを参照する必要があります。
アクションaは、次のうち少なくとも1つに該当する場合にのみ、Xをアクティブに使用します。
aは、Xの要素の読取りまたは書込みを行います。
aはXをロックまたはロック解除し、Xのファイナライザの起動後に発生するXに対するロック・アクションがあります。
aはXへの参照を書き込みます。
aはオブジェクトYのアクティブな使用であり、Xは必ずYからアクセス可能です。
オブジェクトXがdiでファイナライズ可能としてマークされている場合、次のようになります。
Xは、diでアクセス不可としてマークする必要があります。
diは、Xがfinalizableとしてマークされている唯一の場所である必要があります。
ファイナライザの起動後に発生するアクションは、diの後に続く必要があります。
Javaプログラミング言語の実装では、クラスをアンロードできます。
クラスまたはインタフェースは、定義クラス・ローダーがガベージ・コレクタによって再利用される場合のみ、§12.6で説明するようにアンロードできます。
ブートストラップ・ローダーによってロードされたクラスおよびインタフェースは、アンロードできません。
クラス・アンロードは、メモリー使用量の削減に役立つ最適化です。 明らかに、プログラムのセマンティクスは、システムがクラスアンロードなどの最適化を実装するかどうか、およびどのように選択するかに依存してはいけません。 そうしないと、プログラムの移植性が損なわれます。 したがって、クラスまたはインタフェースがアンロードされたかどうかは、プログラムに対して透過的にする必要があります。
ただし、クラスまたはインタフェース Cが定義ローダーに到達可能であった間にアンロードされた場合は、Cがリロードされる可能性があります。 このようなことが起こらないようにすることは決してできませんでした。 クラスが現在ロードされているほかのクラスによって参照されていない場合でも、まだロードされていないクラスまたはインタフェース Dによって参照される可能性があります。 DがCの定義ローダーによってロードされると、その実行によってCがリロードされる可能性があります。
たとえば、クラスにstatic変数(状態が失われる)、静的イニシャライザ(副作用が生じる可能性がある)、またはnativeメソッド(静的状態を保持する可能性がある)がある場合、リロードは透過的でない可能性があります。 さらに、Classオブジェクトのハッシュ値は、そのアイデンティティに依存します。 したがって、一般に、クラスまたはインタフェースを完全に透過的に再ロードすることは不可能です。
ローダーが到達可能である可能性のあるクラスまたはインタフェースをアンロードしてもリロードは発生せず、リロードは透過的ではありませんが、アンロードは透過的である必要があるため、ローダーが到達可能である可能性がある間はクラスまたはインタフェースをアンロードしないでください。 ブートストラップ・ローダーによってロードされたクラスおよびインタフェースをアンロードできないことを推測するために、同様の推論行を使用できます。
また、クラス Cを定義するクラスローダーを再利用できる場合に、クラス Cをアンロードしても安全である理由についても論じる必要があります。 定義ローダーを再利用できる場合、そのローダーへのライブ参照は存在できません(これには、ライブではないがファイナライザによって復活する可能性がある参照が含まれます)。 これは、インスタンスまたはコードからのCを含め、そのローダーによって定義されたクラスへのライブ参照が存在できない場合にのみ、trueになります。
クラス・アンロードは、多数のクラスをロードし、しばらくするとそれらのクラスのほとんどの使用を停止するアプリケーションにとってのみ重要な最適化です。 このようなアプリケーションの主な例はWebブラウザですが、他のアプリケーションもあります。 このようなアプリケーションの特徴は、クラス・ローダーを明示的に使用してクラスを管理することです。 その結果、上述の政策は彼らにとってうまく機能する。
厳密に言えば、クラスのアンロードは単に最適化にすぎないため、この仕様でクラス・アンロードの問題について説明することは不可欠ではありません。 ただし、この問題はきわめて微妙であるため、ここでは明確に説明しています。
プログラムは、1つ以上の実行スレッドで構成されます。 スレッドは、デーモン以外のスレッド、デーモン・スレッドまたはシャットダウン・フックのいずれかです。
スレッドがデーモンのステータスを取得する方法、およびシャットダウン・フックの登録方法の詳細は、リーダーはThreadおよびRuntimeのAPI仕様を参照しています。
スレッドは、(i)そのrunメソッドが正常に完了した場合、または(ii)そのrunメソッドが突然完了し、関連する捕捉されない例外ハンドラ(§11.3)が正常にまたは突然完了した場合、終了します。 実行するコードが残っていないため、スレッドは実行を完了したため、現在のメソッドはありません(JVMS§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コードを実行しません。 スレッドの現在のメソッドは、正常にまたは突然完了しません。 スレッド内のメソッドのfinally句も、捕捉されない例外ハンドラも実行されません。
スレッドが停止フックの実行中にRuntime.haltを起動したためにプログラムの終了が発生した場合、デーモン・スレッドおよびデーモン以外のスレッドに加えて、まだ終了していない停止フックでは、それ以上のJavaコードは実行されません。
Runtime.haltに関するこの異常な状況を除き、プログラム出口は、起動されたシャットダウンフックの終了に依存します。 理由は次の通りです。 デーモン以外のスレッドの数がゼロになったり、スレッドが System.exitまたは Runtime.exitを呼び出したりすると、プログラムがそれ以上実行できなくなり、終了に向かって遷移している可能性があります。ただし、プログラムには補助タスクを実行している他のスレッドがまだ存在している可能性があり、それらのスレッドをトラックで停止することは望ましくありません。 シャットダウン・フックを使用すると、プログラムはアプリケーション固有の方法で正常に中断し、このようなスレッドを停止できます。したがって、シャットダウン・フックがまだ実行中の場合、プログラムはまだ終了していません。
ネイティブ・アプリケーションは、JNI起動APIを使用して、初期クラスのmainメソッド(§12.1)で実行を開始したJavaプログラムが、前述の最初の状況の説明に従って終了するように、Java Virtual Machineを作成および破棄できます。