12.では,Javaプログラムの実行中に発生する活動を規定する。12.は,Java仮想計算機,並びにJavaプログラムを形成する,クラス,インタフェース及びオブジェクトのライフサイクルに関して示す。
Java仮想計算機は,指定されたクラスをロードし,それからこの指定されたクラス内のメソッドmain
を呼び出すことによって,処理を開始する。
12.における概念の導入として,12.1では,main
の実行に含まれローディング,リンキング及び初期化の段階を概説する。
それ以降の項では,ローディング12.2,リンキング12.3及び初期化12.4の詳細を規定する。
12.では,続けて,新しいクラスインスタンスの生成(12.5),クラスインスタンスの終了(12.6)及びクラスの終了(12.7)の手続を規定する。
最後に,クラスのアンローディング(12.8)及び仮想計算機が処理を抜ける時の手続き(12.9)を規定する。
Java仮想計算機は,ある指定されたクラス内のメソッドmain
を呼び出し,文字列の配列とする一つの引数を渡すことによって,実行を開始する。
この規定内の例では,この最初のクラスを典型的にTest
と呼ぶことにする。
最初のクラスをJava仮想計算機に指定する方法は,この規定の範囲外とするが,コマンド行を使用するホスト環境では,コマンド行の引数の一つとしてクラスの完全限定名を指定し,それに続くコマンド行の引数をメソッドmain
への引数として与える文字列として使用することが,典型的である。
例えば,UNIXでの実装では,コマンド行は次のとおりとなる。
java Test reboot Bob Dot Enzo
これは,クラスTest
(名前無しパッケージのクラス)のメソッドmain
を呼び出し,それに四つの文字列 "reboot
","Bob
","Dot
" 及び "Enzo
" を含む一つの配列を渡すことによって,典型的な形で,Java仮想計算機を開始する。
以降の節で詳述する,ローディング,リンキング及び初期化プロセスの例として,ここでは仮想計算機がTest
を実行するために取る段階を概説する。
クラスTest
のロード
クラスTest
のメソッドmain
を実行するために,まず,クラス Test
をロードしていないこと,つまり,仮想計算機が現時点でこのクラスに対するバイナリ表現を含まないことを検出しようとする。次に,仮想計算機は,クラスローダ(20.14)を使用して,そのバイナリ表現を見つけることを試みる。この過程が失敗すれば,エラーが投げられる。このローディングプロセスは,(12.2)で詳しく規定する。
Test
のリンク:検証,準備,(省略可能な)記号解決
Test
をロードした後,main
を呼び出す前に,Test
を初期化しなければならない。
さらに,Test
は,すべての(クラス又はインタフェース)型と同様に,初期化の前にリンクされなければならない。リンキングには,検証,準備及び(省略可能な)記号解決を含む。リンキングは,(12.3)で詳しく規定する。
検証は,ロードしたTest
の表現が,適切な記号表をもった正しい形式であることを検査する。検証は,さらに,Test
を実装するコードが,Java及びJava仮想計算機の意味要件に従っていることも検査する。検証中に問題を検出した場合は,エラーが投げられる。検証は,(12.3.1)で詳しく規定する。
準備は,静的記憶領域,及びメソッド表などの仮想計算機によって内部的に使用されるデータ構造の割当てを含む。準備中に問題を検出した場合は,エラーが投げられる。準備は,12.3.2で詳しく規定する。
記号解決は,記述された他のクラス及びインタフェースをローディングし,参照が正しいことを検査することによって,Test
から他のクラス及びインタフェースへの記号参照を検査するプロセスとする。
記号解決の段階は,初期のリンク時には省略可能とする。実装によっては,非常に早期にリンクされることになっているクラス又はインタフェースからの記号参照を解決してもよい。
これは,たとえクラス及びインタフェースが再帰的にさらに参照され,これらクラス及びインタフェースからのすべての記号参照を解決するのが適切であったとしても許される。(この記号解決は,以降のローディング及びリンキングにおいてエラーを生じるかもしれない。)
この実装の選択は極端な例であるが,C言語の単純な実装で長年行われてきた"静的"リンクの種類に類似している。(これらの実装では,コンパイルしたプログラムは,通常“a.out
”ファイルとして表現する。
このファイルは,そのプログラムが使用するライブラリルーチンへの解決済みのリンクを含んだ,プログラムの完全にリンクされた版を保持している。関与するライブラリルーチンの複製が,“a.out
”ファイルに含まれている。)
この代わりに,実装によっては,記号参照を能動的に使用した場合にだけ解決する方法を選択してもよい。すべての記号参照に対してこの戦略を一貫して使用すると,“もっとも怠慢な”形式の記号解決になる。
この場合,Test
が他のクラスへのいくつかの記号参照をもっていたとすると,それらの参照を使用するときに,一度に一つ解決することもあるし,プログラムの実行中にそれらの参照を一度も使用しなければ,まったく解決しないこともある。
記号解決を実行するときのただ一つの要件は,記号解決でエラーを検出した場合,そのプログラムが,そのエラーに関連するクラス又はインタフェースへのリンクを直接又は間接的に要求するかもしれない動作を実行する時点で,そのエラーを投げなければならないこととする。
上記の“静的な”実装の例では,クラスTest
内で記述するクラス若しくはインタフェース,又はさらに再帰的に参照するクラス及びインタフェースにエラーが関連する場合,ローディング及びリンキングのエラーは,プログラムを実行する前に発生可能となる。
“もっとも怠慢な”記号解決を実装したシステムでは,不正な記号参照を能動的に使用した場合にだけ,これらのエラーが投げられる。
記号解決の過程の詳細については,12.3.3で規定する。
Test
の初期化: 初期化子の実行
これまでに使用してきた例では,仮想計算機はまだクラスTest
のメソッド
main
を実行しようとしているところである。
これは,クラスの能動的使用(12.4.1)の試みであって,そのクラスを初期化した場合にだけ実行できる。
初期化は,すべてのクラス変数の初期化子及びクラスTest
の静的初期化子を,記述した順序で実行することから成る。しかし,Test
を初期化可能となる前に,直接的スーパクラスを初期化しなければならない。同様に,その直接的スーパクラスの直接的スーパクラスが存在する場合は,それを初期化しなければならない。以降も再帰的に同様とする。
もっとも単純な場合は,Test
は暗黙的な直接的スーパクラスとしてObject
をもつ。クラスObject
をまだ初期化していなければ,それをTest
よりも前に初期化しなければならない。クラスObject
はスーパクラスをもたないので,再帰はここで終了する。
クラスTest
にスーパクラスとしてクラスSuper
がある場合,Test
よりも前にSuper
を初期化しなければならない。これには,まだ実行していなければ,Super
のローディング,検証及び準備が必要であって,実装によっては,Super
及び同様に再帰的に続くス−パクラスからの記号参照の解決を含んでもよい。
したがって,初期化では,他の型に関連する同様なエラーを含み,ローディング,リンキング及び初期化のエラーが発生してもよい。
初期化の過程の詳細については,12.4で規定する。
Test.main
の呼出し
最終的に,クラスTest
の初期化(この中で付随するその他のローディング,リンキング及び初期化が発生するかもしれない)が完了した後で,Test
のメソッド
main
を呼び出す。
メソッドmain
は,public
,static
及び void
として宣言しなければならない。このメソッドは,文字列の配列である単一の実引数を受理しなければならない。
ローディング(Loading)は,特定の名前をもつクラス又はインタフェース型のバイナリ形式を見つけだし,そのバイナリ形式から,クラス又はインタフェースを表すClass
オブジェクトを構築する過程を意味する。これは,おそらくその実行の最中に計算(コンパイル)することによっても可能だが,しかしより典型的には,コンパイラによってあらかじめソ−スコ−ドから計算(コンパイル)したバイナリ表現を検索することによって実行する。
クラス又はインタフェースのバイナリ形式は,通常,Java
仮想計算機(The Java Virtual Machine)
で規定するclass
ファイル形式とする。しかし,(13.1)に規定してある要件を満たしていれば,その他の形式も可能とする。
クラスClassLoader
のメソッドdefineClass
を使用して,class
ファイル形式のバイナリ表現からClass
オブジェクトを構築できる。
Java 仮想計算機システムでは,記号参照の解決のために,ロ−ドしたクラス及びインタフェースの内部表を保持していることが望ましい。表の各エントリは,(文字列としての)完全限定クラス名,クラスローダ及びオブジェクトClass
から構成されていることが望ましい。
クラス又はインタフェースへの記号参照を解決するときにはいつでも,そのクラス又はインタフェースのロードに責任がある一つのクラスローダを,必要であれば,識別する。
しかしながら,この表をまず最初に調べるのがよく,そのクラス名及びクラスローダに対するエントリがすでに存在する場合には,そのエントリのクラスオブジェクトを使用し,クラスローダのメソッドを呼び出すことは望ましくない。
その表に適当なエントリが存在しない場合は,そのクラス又はインタフェースの名前を指定して,クラスローダのメソッドloadClass
(20.14.2)を呼び出すのがよい。このメソッドが戻って来たとき,返却値のクラスオブジェクトを使用し,そのクラス名及びクラスローダに対応する新しいエントリを表に作成することが望ましい。
この内部表の目的は,検証過程(12.3.1)で,検証の目的のために,名前及びクラスローダが同じ二つのクラス又はインタフェースは同じと仮定可能とすることにある。
この特性によって,クラスの検証は,そのクラスが使用するすべてのクラス及びインタフェースを,能動的又は受動的にかかわらず,ロードすることなく実行できるようになる。
優れたクラスローダは,この特性を保持する。同じ名前を2回指定すると,良いクラスローダは,毎回同じクラスオブジェクトを返す。しかし,内部表をもたない悪いクラスローダは,この特性に違反し,Javaの型システムの安全性を低下させる。
Java言語の設計の基本原理は,型システムが,Javaで記述したコードによって,ClassLoader
(20.14)及びSecurityManager
(20.17)のような他の敏感なシステムクラスの実装によってさえも,破壊できないこととする。
エントリ内のクラスオブジェクトによって表現されるクラス又はインタフェースをアンローディング(12.8)する後にだけ,この内部表からそのエントリを削除してよい。
ローディング処理は,クラスClassLoader
(20.14)及びそのサブクラスによって実装される。
ClassLoader
の異なるサブクラスは,異なるローディングの方針を実装してもよい。
特に,クラスローダは,クラス及びインタフェースのバイナリ表現をキャッシュしたり,予想される使用方法に基づいてそれらを事前に取り出したり,関連するクラスのグループを一緒にロードしたりしてよい。
これらの活動は,実行中のJavaアプリケーションに対して完全に透過的ではなくてもよい。
例えば,クラスローダによって古いバージョンをキャッシュしているために,新たにコンパイルしたバージョンが見つからないような場合がある。
しかしながら,ローディングエラーが事前取出し又はグループローディングを行わない場合にも発生する可能性があるプログラムにおいて,その場所でだけ,そのエラーを反映するのは,クラスローダの責任とする。
クラスのローディング中にエラーが発生した場合には,その型を(直接又は間接的に)使用するJavaプログラムの内の任意の場所で,クラスLinkageError
のサブクラスの次のいずれか一つのインスタンスを投げる。
ClassCircularityError:
クラス又はインタフェースが,それ自体のスーパクラス又はスーパインタフェース(13.4.4)であるためロードできなかった。- ClassFormatError:
要求したコンパイル済みのクラス又はインタフェースを表現するバイナリデータの形式が不正である。- NoClassDefFoundError:
要求したクラス又はインタフェースの定義を,関連するクラスローダが見つけることができなかった。
ローディングは,新しいデータ構造の割当てを含むので,OutOfMemoryError
で失敗することもある。
クラスローダとの連携で,コード生成は,クラス及びインタフェース型のグループ(多分パッケージ全体)のコードを,それらの型に対するバイナリコ−ドをグル−プとしてローディングすることによって,生成することができる。そのようなグル−プ内のすべての内部記号参照を,そのグル−プをロ−ドする前に記号解決できるようにする形式を設計することができる。
この戦略は,生成するコードをロードする前に,そのグループ内の既知の具体的な型に基づいて最適化可能であってもよい。
こうした手法は,特定の場合には有用だが,その際のクラスファイルの形式は,広範には理解されにくいので,汎用的な手法としては勧められない。
リンキング(Linking)は,クラス及びインタフェース型からバイナリ形式を取り出し,それを実行可能とするために,Java仮想計算機の実行時の状態に結合するプロセスとする。クラス又はインタフェース型は,常にリンクする前にロードする。
リンキングは,検証,準備及び記号参照の解決の三つの異なる処理を含む。
Javaでは,リンキング処理(及び繰り返しによるローディング)を実行する時期について,実装に柔軟性を与えている。ただし,言語の意味規則を遵守すること,クラス又はインタフェースの初期化の前に完全に検証及び準備をすること,並びにリンク中に検出したエラーを,そのエラーに関連するクラス又はインタフェースへのリンクを必要とする動作を実行した時点でプログラムが投げること,という条件を遵守しなければならない。
例えば,実装では,クラス又はインタフェース内の各記号の参照を,それを使用した場合にだけ個々に解決する(怠慢又は遅延解決)方法か,又はクラスを検証するときに一度にすべてを解決する(静的解決)方法のいずれかを選択してもよい。これは,実装によっては,クラス又はインタフェースを初期化した後も解決処理が継続する場合があることを意味する。
リンキングには,新しいデータ構造の割当てが含まれるので,OutOfMemoryError
で失敗してもよい。
検証(Veritication)では,クラス又はインタフェースのバイナリ表現が構造的に正しいかどうかを確認する。例えば,それぞれの命令が正しい演算コードをもっていること,それぞれの分岐命令が他の命令の途中ではなく先頭へと分岐していること,それぞれのメソッドが構造的に正しいシグネチャをもっていること,それぞれの命令がJava言語の型規則に従っていることなどを調べる。
検証プロセスは,”Java仮想計算機仕様書(The Java Virtual Machine Specification)”で詳しく規定する。
検証中にエラーが発生した場合は,Javaプログラム内のそのクラスの検証を引き起こした場所で,クラスLinkageError
のサブクラスとする,次のインスタンスを投げる。
- VerifyError:
クラス又はインタフェースのバイナリ定義が,Java言語の意味規則に従っていること,及びJava仮想計算機の完全性を破壊できないこと,を検証するために,必要とされる検査項目の集合に合格しなかった(例については,13.4.2, 13.4.4, 13.4.8 及び 13.4.16 を参照のこと)。
準備(Preparation)は,クラス又はインタフェースのstatic
フィールド(クラス変数及び定数)の生成,及びそれらのフィールドの標準デフォルト値への初期化を含む(4.5.4)。
準備では,Javaコードの実行を要求しない。static
フィールドの明示的な初期化子は,準備ではなく初期化(12.4)の一部として実行する。
Javaの実装は,準備中に次のエラーを検出しなければならない。
- AbstractMethodError
: abstract
として宣言していないクラスに,abstract
メソッドが存在する。
例えばこれは,元々abstract
ではなかったのだが,今はabstract
メソッドとなっている宣言を継承している他のクラスをコンパイルした後で,そのメソッドの宣言をabstract
(13.4.15)に変更した場合に発生する。
このようなエラーを検出した場合は,AbstractMethodError
のインスタンスを,Javaプログラム内のそのクラスの準備を引き起こした時点で投げなければならない。
Java仮想計算機の実装によっては,以降のクラス又はインタフェースの演算をより効率的にするために,準備時に追加のデータ構造を事前に処理してもよい。特に有用なデータ構造は,Wメソッド表W又はスーパクラスの検索を必要とせずにクラスのインスタンス上でメソッドを呼出し可能とする,それ以外のデータ構造とする。
Javaバイナリファイルは,他のクラス及びインタフェースの完全限定名を使用し,他のクラス及びインタフェース,並びにそれらのフィールド,メソッド及びコンストラクタを記号的に参照する(13.1)。
フィールド及びメソッドに対して,これらの記号参照は,そのフィールド又はメソッド自体の名前に加え,そのフィールド又はメソッドを宣言しているクラス又はインタフェース型の名前を適切な型情報とともに含む。
記号参照が使用可能となる前に,まず記号解決(resolution)を行わなければならない。記号解決は,記号参照が正しいことを検査し,さらに,普通は,その参照を繰り返し使用する場合に一層効率的に処理できる直接参照に置換する。
記号解決中にエラーが発生した場合は,エラーを投げる。普通このエラ−は,クラスIncompatibleClassChangeError
の次に示すサブクラスのうちの一つのインスタンスとするが,クラスIncompatibleClassChangeError
のこれ以外のサブクラスのインスタンスであってもよいし,クラスIncompatibleClassChangeError
自体のインスタンスであってもよい。
このエラーは,その型への記号参照を直接又は間接的に使用するプログラム内の任意の時点で投げてよい。
- IllegalAccessError
:
フィールドの使用若しくは代入,メソッドの起動又はクラスのインスタンスの生成を指定する記号参照を検出したが,その参照を含むコードがそれらにアクセスできない。
理由としては,そのフィールド又はメソッドを,private
,protected
若しくは(public
ではない)デフォルトアクセスとして宣言しているため,又はそのクラスをpublic
として宣言していないためによる。
例えばこれは,元々public
として宣言していたフィールドを,そのフィールドを参照する他のクラスをコンパイルした後で,private
に変更した場合に発生する(13.4.6)。- InstantiationError
:
クラスのインスタンス生成式内で使用している記号参照を検出したが,その参照が結果的にインタフェース又はabstract
クラスを参照しているためインスタンスを生成できない。
例えばこれは,元々abstract
ではなかったクラスを,そのクラスを参照する他のクラスをコンパイルした後で,abstract
に変更した場合に発生する(13.4.1)。- NoSuchFieldError
:
特定のクラス又はインタフェースの特定のフィールドを参照する記号参照を検出したが,そのクラス又はインタフェースは,その名前のフィールドを宣言していない(特にフィールドは,単純にそのクラス又はインタフェースの継承されたフィールドであるだけでは十分ではない)。
例えばこれは,そのフィールドを参照する他のクラスをコンパイルした後で,クラスからフィールドの宣言を削除した場合に発生する(13.4.7)。- NoSuchMethodError
:
特定のクラス又はインタフェースの特定のメソッドを参照する記号参照を検出したが,そのクラス又はインタフェースは,そのシグネチャのメソッドを宣言していない(特にメソッドは,単純にそのクラス又はインタフェースの継承されたメソッドであるだけでは十分ではない)。
例えばこれは,そのメソッドを参照する他のクラスをコンパイルした後で,クラスからメソッドの宣言を削除した場合に発生する(13.4.12)。
さらに,あるクラスでnative
メソッドを宣言しており,その実装が見つからない場合,UnsatisfiedLinkError
(LinkageError
のサブクラス)を投げてもよい。
このエラーは,仮想計算機が使用している記号解決の方法の種類に応じて,メソッドを使用したとき又はそれ以前に発生する(12.3)。
特殊な(非標準の)バイナリ形式(13.1)を使用する実装では,ある型のグループ内の記号参照を,そのグループをロードする前に記号解決してもよい(12.2.2)。
これは,従来の“リンケージ編集”に対応している。リンケージ編集を行わなくとも,Javaの実装には高い柔軟性をもつ。ある型からのすべての記号参照を,その型についての最初のリンケ−ジ処理の時点で記号解決してもよいし,又はそれぞれの記号参照の解決を,その参照を最初に使用する時点まで遅らせてもよい。
Javaの実装に許しているリンケージ処理の柔軟性は,リンケージエラーが発生しない正しい形式のJavaプログラムには影響しないことに注意すること。
クラスの初期化は,そのクラスで宣言している静的初期化子及びstatic
フィールド(クラス変数)の初期化子の実行で構成される。インタフェースの初期化は,そのインタフェースで宣言しているフィールド(定数)の初期化子の実行で構成される。
クラスを初期化する前に,そのスーパクラスを初期化しなければならないが,そのクラスによって実装するインタフェースを初期化する必要はない。同様に,インタフェースを初期化する前に,そのインタフェースのス−パインタフェースを初期化する必要はない。
クラス又はインタフェース型Tは,最初に能動的使用(active use)をしたときに初期化(initialized)する。これは,次による。
final
及びstatic
と宣言されたフィールドであって,コンパイル時の定数式の値によって初期化したフィールドとする(15.27)。
Javaの規定では,定数フィールドへの参照は,コンパイル時に,コンパイル時の定数値の複製への参照として解決しなければならないので,これらのフィールドの使用は能動的使用とならない。詳細な規定については,13.4.8を参照のこと。
型のその他のすべての使用は,受動的使用(passive uses)とする。
ここでの意図は,クラス又はインタフェース型にはそれを一貫した状態にする初期化子の集合があって,この状態が他のクラスから観察される場合の初期状態ということとする。
静的初期化子及びクラス変数初期化子は記述した順序で実行し,そのクラス内の位置的にその使用の後に宣言したクラス変数は,そのクラス変数が有効範囲内であっても参照してはならない(8.5)。
この制限は,コンパイル時に最大限の循環した初期化又はその他の不正な形式の初期化を検出するために設計されている。
8.5の例に示すように,初期化コードを制限していないことによって,その初期化式を評価する前のそれがまだデフォルト値のままのときに,クラス変数の値を調べる例を作成できる。
しかし,このような例は,実際にはほとんどない(同様の例は,インスタンス変数の初期化についても作成できる。12.5の最後の例を参照のこと)。
Java言語では,これらの初期化子に言語としてのあらゆる能力を与えているので,プログラマは注意して使用しなければならない。
この能力によってコード生成には余分な負荷がかかるが,しかしこの負荷は,Javaが並行的であるためにいずれにしても発生する(12.4.3)。
あるクラスを初期化する前に,そのスーパクラスを初期化していない場合は,そのスーパクラスを初期化する。
次に例を示す。
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
が初期化された後に初期化される。
フィールドへの参照は,そのフィールドが,そのサブクラス,サブインタフェース又はインタフェースを実装するクラスの名前を介して参照される場合であっても,そのフィールドを実際に宣言したクラス又はインタフェースだけの能動的使用とする。次に例を示す。
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
の能動的使用ではない。
インタフェースの初期化は,それ自体では,そのス−パインタフェースの初期化を必要としない。この例を次に示す。
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
への参照は,コンパイル時の定数であるフィールドへの参照とする。したがって,それはI
の初期化を引き起こさない。K.j
への参照は,インタフェースJ
内で実際に宣言しているフィールドへの参照であって,それはコンパイル時の定数ではない。
これは,インタフェースJ
のフィールドの初期化を引き起こすが,ス−パインタフェースI
のフィールド及びインタフェースK
のフィールドの初期化は引き起こさない。
インタフェースJ
のフィールドj
を参照するために,K
という名前を使用しているにもかかわらず,インタフェースK
は能動的使用ではない。
Javaはマルチスレッドであるため,ある他のスレッドが,同時に同じクラス又はインタフェースの初期化を試みる場合があるので,クラス又はインタフェースの初期化には注意深い同期化を必要とする。
あるクラス又はインタフェースの初期化は,そのクラス又はインタフェースの初期化の一部として再帰的に要求される可能性もある。
例えば,クラスA内の変数初期化子は,無関係のクラスB内のメソッドを呼び出す場合があり,これが,結果としてクラスAのメソッドを呼び出す可能性がある。
Java仮想計算機の実装は,次の手順を使用することによって,同期及び再帰的な初期化を実行する責任がある。
ここで,そのオブジェクトClass
は既に検証及び準備済みであること,並びにそのオブジェクトClass
は,次の四つの状態の一つを示す状態を含むことを仮定する。
Class
は,検証及び準備済みであるが初期化済みではない。Class
は,ある特定スレッドTによって初期化実行中とする。
Class
は,完全に初期化済みであり使用の準備が完了している。Class
は,エラー状態にある。恐らく,検証又は準備段階で失敗しているか,又は初期化を実行して失敗したためとする。Class
を同期(14.17)する。これは,実行中のスレッドが,そのオブジェクトに対するロックを獲得できるまで待つことを含む(17.13)。
Class
上でwait
(20.1.6)する(一時的にそのロックを解除する)。現在のスレッドのwait
が解放すると,この段階を繰り返す。
Class
のロックを解除し,正常に終了する。
Class
のロックを解除し,正常に終了する。
Class
がエラー状態にある場合,初期化は不可能とする。そのオブジェクトClass
のロックを解除し,NoClassDefFoundError
を投げる。
Class
の初期化が現在のスレッドで進行中であるという事実を記録し,そのオブジェクトClass
のロックを解除する。
Class
が,インタフェースでなくクラスを表し,このクラスのスーパクラスを未だ初期化していない場合,そのスーパクラスにこの全体の手順を繰り返し実行する。
必要な場合は,最初にそのスーパクラスを検証及び準備する。スーパクラスの初期化が例外を投げることによって中途完了した場合,そのオブジェクトClass
をロックし,それにエラーのラベル表示を行い,すべての待機しているスレッド(20.1.10)にそれを通知し,ロックを解除し,それからそのスーパクラスの初期化の結果と同じ例外を投げる。final
クラス変数及びインタフェースのフィールドの値を,最初に初期化することは除外する(8.3.2.1, 9.3.1,13.4.8)。Class
をロックし,それを完全に初期化したことを表示し,すべての待機しているスレッドにそれを通知し(20.1.10),ロックを解除し,それから正常にこの手順を終了する。
Error
又はそのサブクラスの一つでない場合,クラスExceptionInInitializerError
の新しいインスタンスを,実引数としてEを伴って生成し,それから次の段階のEの所でこのオブジェクトを使用する。
しかし,OutOfMemoryError
が発生したために,ExceptionInInitializerError
の新しいインスタンスが生成できない場合,その代わりに,次の段階のEの所でオブジェクトOutOfMemoryError
を使用する。
Class
をロックし,エラー表示をし,すべての待機しているスレッドにそれを通知し(20.1.10),ロックを解除し,それから理由E又は以前の段階で定義した代替の理由によって,この手順を中途完了する。
コード生成は,クラス又はインタフェースの初期化可能な場所を,前述した初期化手続きの呼出しを挿入することによって,保持する必要がある。
この初期化手続きが正常に完了し,オブジェクトClass
を完全に初期化し使用準備ができれば,初期化手続きの呼出しは不要となる。たとえばパッチによってそれを解除したり,コードを再生成したりすることによって,コードから削除することができる。
関連する型のグループに対する初期化の順序が決定できれば,ある場合には,コンパイル時の解析によって,ある型が生成したコードによって初期化済みであるかどうかの多くの検査を除外することができる。
ただし,それらの解析は,Java言語が並行処理であって,初期化コードが非制限という事実を十分に考慮する必要がある。
新しいクラスインスタンスは,次の状況のいづれかが発生したときに,明示的に生成する。
Class
のnewInstance
メソッド(20.3.6)の呼出しは,メソッドが呼び出されたオブジェクトClass
によって表されるクラスの新しいインスタンスを生成する。
新しいクラスインスタンスは,次の状況で暗黙的に生成してもよい。
String
リテラル(3.10.5)を含むクラス又はインタフェースのローディングは,そのリテラルを表すために新しいオブジェクトString
(20.12)を生成してもよい。(同じString
が以前に既に生成済みであれば,これは発生してはならない(10.5)。)
String
を生成することが多い。文字列連結演算子は,プリミティブ型の値に対して一時的なラッパ−オブジェクトを生成してもよい。
これらの状況のそれぞれは,クラスインスタンス生成プロセスの一部として,指定した実引数(無い場合もある)を伴って呼び出すべき特定のコンストラクタを識別する。
新しいクラスインスタンスを生成するときには,隠されたすべてのインスタンス変数を含む,そのクラス型で宣言してあるすべてのインスタンス変数及びそのクラス型のそれぞれのスーパクラスに宣言してあるすべてのインスタンス変数に対して,十分な大きさのメモリ空間を割り当てる。
そのオブジェクトにメモリを割り当てるのに十分な領域がない場合,クラスインスタンスの生成は,OutOfMemoryError
で中途完了する。
そうでなければ,その新しいオブジェクトのすべてのインスタンス変数は,スーパクラスで宣言したものを含み,それらのデフォルト値に初期化される(4.5.4)。
新しく生成したオブジェクトへの参照を結果として返却する直前に,新しいオブジェクトを初期化するために,指定したコンストラクタを,次の手順を使用して実行する。
this
を使用した),実引数の評価及びそのコンストラクタ呼出しを,これらの同じ五つの手順を使用して,反復的に処理する。
そのコンストラクタの呼出しが中途完了した場合,この手順は,同じ理由で中途完了する。そうでなければ,手順5に継続する。
this
を使用した)。このコンストラクタが,Object
以外のクラスに対するものである場合,このコンストラクタは,スーパクラスのコンストラクタの明示的又は暗黙的な呼出し(super
を使用した)で始まる。
実引数の評価及びそのコンストラクタ呼出しを,これらの同じ五つの手順を使用して,反復的に処理する。そのコンストラクタの呼出しが中途完了した場合,この手順は,同じ理由で中途完了する。そうでなければ,手順4に継続する。
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
は,コンストラクタを宣言していないので,次の形式のデフォルトコンストラクタをJavaコンパイラが自動的に提供する。
ColoredPoint() { super(); }
それから,このコンストラクタは,コンストラクタPoint
を実引数なしで呼び出す。コンストラクタPoint
は,コンストラクタの呼出しで始まらないので,コンパイラが,そのスーパクラスのコンストラクタの暗黙的な実引数なしの呼出しを提供する。 それは,まるで次のとおりに記述したものとなる。
Point() { super(); x = 1; y = 1; }
したがって,Object
に対する,実引数を取らないコンストラクタを呼び出す。
クラスObject
は,スーパクラスをもたないので,反復はここで終了する。
次に,Object
のすべてのインスタンス変数初期化子及び静的初期化子を呼び出す。
さらに,Object
の実引数なしのコンストラクタの本体を実行する。Object
は,こうしたコンストラタを宣言していないので,コンパイラがデフォルトのコンストラクタを供給する。この特別な場合では,それは次のとおりとなる。
Object() { }
このコンストラクタは,作用も返却値もなしで実行する。
次に,クラスPoint
のインスタンス変数に対するすべての初期化子を実行する。このとき,x
及びy
の宣言は,いかなる初期化式も指定していないので,この例のこの段階に対しては,何の動作も要求しない。それから,コンストラクタPoint
の本体を実行し,x
を1
に,y
を1
に設定する。
次に,クラスColoredPoint
のインスタンス変数に対する初期化子を実行する。この段階は,color
に値0xFF00FF
を代入する。最後に,コンストラクタColoredPoint
の本体の残りを実行する(super
の呼出しの後の部分)。本体の残りには文は無いので,それ以上の動作は要求されず,初期化は完了する。
C++と異なり,Java言語は,新しいクラスインスタンスの生成中のメソッドディスパッチのための変更規則を規定しない。初期化中のオブジェクト内のサブクラス内の上書きされているメソッドを呼び出した場合,新しいオブジェクトを完全に初期化する前であっても,これらの上書きするメソッドを使用する。したがって,コンパイル及び実行例は,次のとおりとする。
class Super { Super() { printThree(); } void printThree() { System.out.println("three"); } } class Test extends Super { int indiana = (int)Math.PI; // That is, 3 public static void main(String[] args) { Test t = new Test(); t.printThree(); } void printThree() { System.out.println(indiana); } } 次の出力を得る。0
3
これは,クラスSuper
に対するコンストラクタのprintThree
の呼出しが,クラスSuper
のprintThree
の定義を呼び出さず,むしろクラスTest
内で上書きを定義しているprintThree
を呼び出すことを示している。
したがって,このメソッドは,Test
のフィールド初期化子を実行する前に動作する。これが,出力の最初の値が0
となる理由である。これは,Test
のフィールドthree
を初期化したデフォルト値である。
メソッドmain
内のprintThree
の後の呼出しは,printThree
の同じ定義を呼び出すが,その時点までには,インスタンス変数three
に対する初期化子を実行している。そのため値3
を出力する。
コンストラクタ宣言の詳細については,8.6を参照のこと。
クラスObject
は,finalize
(20.1.11)と呼ぶprotected
メソッドをもつ。
このメソッドは,他のクラスによって上書きできる。
あるオブジェクトに対して呼び出すことができるfinalize
の特定の定義を,そのオブジェクトの終了化子(finalizer)と呼ぶ。
オブジェクトに対する記憶域をガーベジコレクタによって再利用する前に,Java 仮想計算機は,そのオブジェクトの終了化子を呼び出す。
終了化子は,自動記憶域管理によって自動的に解放できないリソース(ファイル記述子又はオペレーティングシステムのグラフィックコンテンツなど)を解放するための機会を提供する。
そのような状況では,単純に,あるオブジェクトが使用していたメモリを再利用するということは,それが保有していた資源を再利用することを保証するものではない。
Java言語は,オブジェクトに対する記憶域を再利用する前に終了化子が動作するという点を除いて,終了化子をどれぐらい早く呼び出すかを規定していない。
Java言語は,どのスレッドがある特定のオブジェクトに対して終了化子を呼び出すかをも規定しない。終了化子実行中に未捕捉の例外が投げられた場合,その例外は無視され,そのオブジェクトの終了化が終了する。
クラスObject
内に宣言されているメソッドfinalize
は,何の動作も実行しない。
しかしながら,クラスObject
がメソッドfinalize
を宣言しているという事実は,任意のクラスに対するメソッドfinalize
が,そのスーパクラスに対するメソッドfinalize
を常に呼び出すことができるということを意味する。それは,通常,良い習慣である。
(コンストラクタと異なり,終了化子は,スーパクラスに対する終了化子を自動的に呼び出さない。それらの呼出しは,明示的にコードしなければならない。)
実行効率のために,処理系は,クラスObject
のメソッドfinalize
を上書きしない,又は自明な方法でそれを上書きするクラスを覚えていてもよい。次に例を示す。
protected void finalize() throws Throwable { super.finalize(); }
処理系は,このようなオブジェクトは,上書きしていない終了化子をもつとして扱い,さらに12.6.1で規定したように,より効率的にそれらを終了することを推奨する。
終了化子は,他のメソッドと同じように明示的に呼び出してもよい。
すべてのオブジェクトは,二つの属性によって分類する。つまり,オブジェクトは,到達可能(reachable),終了化子到達可能(finalizer-reachable)又は到達不能(unreachable)としてよく,さらに非終了化(unfinalized),終了化可能(finalizable)又は終了化(finalized)としてもよい。
到達可能(reachable)オブジェクトは,すべてのライブスレッドからのすべての潜在的に継続する計算でアクセスできるすべてのオブジェクトとする。
プログラムの最適化用変形は,到達可能なオブジェクトの数を,単純に到達可能であると考えられるオブジェクトの数よりも少ない数に削減するように設計できる。
例えば,コンパイラ又はコード生成は,そのようなオブジェクトに対する記憶域を潜在的にすぐに再利用できるようにするために,もはや使用されない変数又は仮引数を,明示的又は暗黙的に,null
に設定することを選択してもよい。
終了化子到達可能(finalizer-reachable)オブジェクトは,いくつかの参照の連鎖を通じて,いくつかの終了化可能オブジェクトからは到達できるが,ライブスレッドからは到達できないものとする。
到達不能(unreachable)オブジェクトは,いづれの方法でも到達できないものとする。
非終了化(unfinalized)オブジェクトは,自動的に呼び出す終了化子をもたないものとする。
終了化(finalized)オブジェクトは,自動的に呼び出す終了化子をもつものとする。
終了化可能(finalizable)オブジェクトは,自動的に呼び出す終了化子をもたないが,Java 仮想計算機が,その終了化子を最終的に自動的に呼び出すものとする。
オブジェクトのライフサイクルは,次の遷移図に従うものとする。
ここで,終了化子到達可能は,f-reachableと略している。
オブジェクトを最初に生成したとき(A),それは到達可能及び非終了化とする。
あるオブジェクトへの参照は,プログラム実行中に放棄されるので,到達可能であったオブジェクトは,終了化子到達可能(B,C,D)又は到達不能(E,F)になる。
(終了化子到達可能オブジェクトは,直接的には到達不能にならないことに注意すること。後述するように,それはそこから到達できるような終了化子を呼び出したときに到達可能になる。)
Java 仮想計算機が,非終了化オブジェクトが終了化子到達可能又は到達不能になったことを検出した場合,そのオブジェクトを終了化可能(G,H)とラベル表示してもよい。
さらに,そのオブジェクトが到達不能であった場合,それは終了化可能になる(H)。
Java 仮想計算機が,終了化オブジェクトが到達不能になったことを検出した場合,そのオブジェクトは再び到達可能にはならないので,そのオブジェクトが占有している記憶域を再利用してもよい。(I)
任意の時点で,Java 仮想計算機は,終了化可能なオブジェクトを取り出し,それに終了化とラベル表示を行い,それからいずれかのスレッド内のそのメソッドfinalize
を呼び出してもよい。これによって,そのオブジェクトは終了化及び到達可能になり(J,K),さらにそれは,終了化子到達可能であった他のオブジェクトを,再び到達可能にしてもよい。(K,M,N)
終了化可能オブジェクトは,同時に到達不能とすることはできない。その終了化子を最終的に呼び出すので,到達可能となる。その上で,その終了化子を実行しているスレッドは,そのオブジェクトに対するアクセスを,this
(15.7.2)としてもつ。このように,あるオブジェクトに対しては,八つの可能な状態だけが実際に存在する。
オブジェクトを終了した後,自動記憶域管理が到達可能でないと決定するまでは,それ以上の動作は実行されない。
オブジェクトが非終了化(unfinalized)状態から終了化可能(finalizable)状態を経由して終了化(finalized)の状態に進行する方式であるため,そのオブジェクトが終了した後に再び到達可能になったとしても,それぞれのオブジェクトに対してメソッドfinalize
がJava 仮想計算機によって一度以上自動的に呼び出されることはない。
終了化子の明示的な呼出しは,オブジェクトの現在の状態を無視する。さらに,オブジェクトの状態を非終了化又は終了化可能から終了化に変更することはない。
クラスが,クラスObject
のメソッドfinalize
を上書きしない場合(又は前述したように,自明な方法だけで上書きする場合)及びそれらのクラスのインスタンスが到達不能になる場合,それらが到達不能になったという2番目の決定を待たずに,速やかに廃棄してもよい。この戦略は,遷移図の中に破線の矢印(O)で示している。
Javaプログラマは,終了化子が到達可能であっても,抜出し中の終了化(12.9)の間に,終了化子を自動的に呼び出すことが可能なことに注意することが望ましい。
さらに,終了化子は通常のメソッドと同様に明示的に呼び出すこともできる。
したがって,メソッドfinalize
は,それらがあらゆる場合に動作できるように,その設計は簡潔さを保っており,防衛的にプログラムするのがよい。
Javaは,メソッドfinalize
の呼出しについては,順序性を強制しない。終了化子は,任意の順序で呼び出してもよいし,同時に呼び出してもよい。
例えば,非終了化オブジェクトの循環的にリンクされたグループが到達不能(又は終了化子到達可能)になった場合,すべてのオブジェクトをまとめて終了化可能にしてもよい。
最終的には,これらのオブジェクトの終了化子は,任意の順序で呼び出してもよいし,マルチスレッドを使用して同時に呼び出してもよい。
自動記憶域管理が,後に,そのオブジェクトが到達不能であることを検出すると,それらの記憶域は再利用できる。
すべてのオブジェクトが到達不能になったとき.あるオブジェクトの集合に対して,指定した順序で終了化子的なメソッドの集合の呼出しを引き起こすような,Javaクラスを実装することは簡単である。
あるクラスが,実引数を取らず,結果を返さない次のクラスメソッドclassFinalize
を宣言した場合を考える。
static void classFinalize() throws Throwable { . . . }
このメソッドは,クラスをアンロードする前に呼び出される(12.8)。
オブジェクトに対するメソッドfinalize
と同様に,このメソッドは,自動的に一度だけ呼び出される。
このメソッドは,任意に private
,protected
又は
public
で宣言してもよい。
Java 仮想計算機は,クラスをアンロード(unloaded)する機構を提供してもよい。そのような機構の詳細は,この版のJava言語規程では規定しない。一般的に,関連するクラス及びインタフェース型のグループを,まとめてアンロードする。
例えば,これは,特定のクラスローダを使用することによってロードした,関連する型のグループをアンロードするために使用できる。そのようなグループは,そのすべてのクラスが,例えば,HotJavaのようなJavaベースのブラウザの単一アプレットを実装するクラスから構成されるかもしれない。
クラスは,そのいずれかのインスタンスが到達可能である間は,アンロードしてはならない(12.6)。
クラス又はインタフェースは,それを表すオブジェクトClass
が到達可能である間は,アンロードしてはならない。
クラスfainalizer
(12.7) を宣言するクラスは,それらをアンロードする前にこれらのfinalizer
を実行する。
Java 仮想計算機は,次の二つのうちのいづれかが発生したとき,そのすべての活動を終了し,抜け出す(exits)。
Runtime
又はクラスSystem
のメソッドexit
(20.16.2)を呼び出し,その終了操作を,セキュリティマネージャ (20.17.13)が禁止していない。
Javaプログラムは,終了化子をもつすべてのオブジェクト及びクラスfinalizer
をもつすべてのクラスの終了化子のうち,まだ自動的に呼び出していない終了化子を,仮想計算機を抜け出す前に実行することを指定できる。
これは,クラスSystem
のメソッドrunFinalizersOnExit
を,実引数true
を指定して呼び出すことによって実行する。
デフォルトは,抜出し時に終了化子を実行しないこととする。この振舞いは,runFinalizersOnExit
を実引数false
を指定して呼び出すことによって回復できる。
メソッドrunFinalizersOnExit
の呼出しは,呼出し元がexit
できる場合にだけ許可され,そうでなければSecurityManager
(20.17)によって拒否される。