目次| |



12.実行

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)を規定する。

12.1 仮想計算機の開始

Java仮想計算機は,ある指定されたクラス内のメソッドmainを呼び出し,文字列の配列とする一つの引数を渡すことによって,実行を開始する。 この規定内の例では,この最初のクラスを典型的にTestと呼ぶことにする。

最初のクラスをJava仮想計算機に指定する方法は,この規定の範囲外とするが,コマンド行を使用するホスト環境では,コマンド行の引数の一つとしてクラスの完全限定名を指定し,それに続くコマンド行の引数をメソッドmainへの引数として与える文字列として使用することが,典型的である。 例えば,UNIXでの実装では,コマンド行は次のとおりとなる。


java Test reboot Bob Dot Enzo 


これは,クラスTest(名前無しパッケージのクラス)のメソッドmainを呼び出し,それに四つの文字列 "reboot","Bob","Dot" 及び "Enzo" を含む一つの配列を渡すことによって,典型的な形で,Java仮想計算機を開始する。

以降の節で詳述する,ローディング,リンキング及び初期化プロセスの例として,ここでは仮想計算機がTestを実行するために取る段階を概説する。

12.1.1 クラスTestのロード

クラスTestのメソッドmainを実行するために,まず,クラス Test をロードしていないこと,つまり,仮想計算機が現時点でこのクラスに対するバイナリ表現を含まないことを検出しようとする。次に,仮想計算機は,クラスローダ(20.14)を使用して,そのバイナリ表現を見つけることを試みる。この過程が失敗すれば,エラーが投げられる。このローディングプロセスは,(12.2)で詳しく規定する。

12.1.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で規定する。

12.1.3 Testの初期化: 初期化子の実行

これまでに使用してきた例では,仮想計算機はまだクラスTestのメソッド mainを実行しようとしているところである。 これは,クラスの能動的使用(12.4.1)の試みであって,そのクラスを初期化した場合にだけ実行できる。

初期化は,すべてのクラス変数の初期化子及びクラスTestの静的初期化子を,記述した順序で実行することから成る。しかし,Testを初期化可能となる前に,直接的スーパクラスを初期化しなければならない。同様に,その直接的スーパクラスの直接的スーパクラスが存在する場合は,それを初期化しなければならない。以降も再帰的に同様とする。 もっとも単純な場合は,Testは暗黙的な直接的スーパクラスとしてObjectをもつ。クラスObjectをまだ初期化していなければ,それをTestよりも前に初期化しなければならない。クラスObjectはスーパクラスをもたないので,再帰はここで終了する。

クラスTestにスーパクラスとしてクラスSuperがある場合,Testよりも前にSuperを初期化しなければならない。これには,まだ実行していなければ,Superのローディング,検証及び準備が必要であって,実装によっては,Super及び同様に再帰的に続くス−パクラスからの記号参照の解決を含んでもよい。

したがって,初期化では,他の型に関連する同様なエラーを含み,ローディング,リンキング及び初期化のエラーが発生してもよい。

初期化の過程の詳細については,12.4で規定する。

12.1.4 Test.mainの呼出し

最終的に,クラスTestの初期化(この中で付随するその他のローディング,リンキング及び初期化が発生するかもしれない)が完了した後で,Testのメソッド mainを呼び出す。

メソッドmainは,publicstatic 及び void として宣言しなければならない。このメソッドは,文字列の配列である単一の実引数を受理しなければならない。

12.2 クラス及びインタフェースのローディング

ローディング(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)する後にだけ,この内部表からそのエントリを削除してよい。

12.2.1 ローディング処理

ローディング処理は,クラスClassLoader(20.14)及びそのサブクラスによって実装される。 ClassLoaderの異なるサブクラスは,異なるローディングの方針を実装してもよい。 特に,クラスローダは,クラス及びインタフェースのバイナリ表現をキャッシュしたり,予想される使用方法に基づいてそれらを事前に取り出したり,関連するクラスのグループを一緒にロードしたりしてよい。 これらの活動は,実行中のJavaアプリケーションに対して完全に透過的ではなくてもよい。 例えば,クラスローダによって古いバージョンをキャッシュしているために,新たにコンパイルしたバージョンが見つからないような場合がある。 しかしながら,ローディングエラーが事前取出し又はグループローディングを行わない場合にも発生する可能性があるプログラムにおいて,その場所でだけ,そのエラーを反映するのは,クラスローダの責任とする。

クラスのローディング中にエラーが発生した場合には,その型を(直接又は間接的に)使用するJavaプログラムの内の任意の場所で,クラスLinkageErrorのサブクラスの次のいずれか一つのインスタンスを投げる。


ローディングは,新しいデータ構造の割当てを含むので,OutOfMemoryErrorで失敗することもある。

12.2.2 ローディング:コード生成との関係

クラスローダとの連携で,コード生成は,クラス及びインタフェース型のグループ(多分パッケージ全体)のコードを,それらの型に対するバイナリコ−ドをグル−プとしてローディングすることによって,生成することができる。そのようなグル−プ内のすべての内部記号参照を,そのグル−プをロ−ドする前に記号解決できるようにする形式を設計することができる。 この戦略は,生成するコードをロードする前に,そのグループ内の既知の具体的な型に基づいて最適化可能であってもよい。 こうした手法は,特定の場合には有用だが,その際のクラスファイルの形式は,広範には理解されにくいので,汎用的な手法としては勧められない。

12.3 クラス及びインタフェースのリンキング

リンキング(Linking)は,クラス及びインタフェース型からバイナリ形式を取り出し,それを実行可能とするために,Java仮想計算機の実行時の状態に結合するプロセスとする。クラス又はインタフェース型は,常にリンクする前にロードする。 リンキングは,検証,準備及び記号参照の解決の三つの異なる処理を含む。

Javaでは,リンキング処理(及び繰り返しによるローディング)を実行する時期について,実装に柔軟性を与えている。ただし,言語の意味規則を遵守すること,クラス又はインタフェースの初期化の前に完全に検証及び準備をすること,並びにリンク中に検出したエラーを,そのエラーに関連するクラス又はインタフェースへのリンクを必要とする動作を実行した時点でプログラムが投げること,という条件を遵守しなければならない。

例えば,実装では,クラス又はインタフェース内の各記号の参照を,それを使用した場合にだけ個々に解決する(怠慢又は遅延解決)方法か,又はクラスを検証するときに一度にすべてを解決する(静的解決)方法のいずれかを選択してもよい。これは,実装によっては,クラス又はインタフェースを初期化した後も解決処理が継続する場合があることを意味する。

リンキングには,新しいデータ構造の割当てが含まれるので,OutOfMemoryErrorで失敗してもよい。

12.3.1 バイナリ表現の検証

検証(Veritication)では,クラス又はインタフェースのバイナリ表現が構造的に正しいかどうかを確認する。例えば,それぞれの命令が正しい演算コードをもっていること,それぞれの分岐命令が他の命令の途中ではなく先頭へと分岐していること,それぞれのメソッドが構造的に正しいシグネチャをもっていること,それぞれの命令がJava言語の型規則に従っていることなどを調べる。

検証プロセスは,”Java仮想計算機仕様書(The Java Virtual Machine Specification)”で詳しく規定する。

検証中にエラーが発生した場合は,Javaプログラム内のそのクラスの検証を引き起こした場所で,クラスLinkageErrorのサブクラスとする,次のインスタンスを投げる。


12.3.2 クラス又はインタフェース型の準備

準備(Preparation)は,クラス又はインタフェースのstaticフィールド(クラス変数及び定数)の生成,及びそれらのフィールドの標準デフォルト値への初期化を含む(4.5.4)。 準備では,Javaコードの実行を要求しない。staticフィールドの明示的な初期化子は,準備ではなく初期化(12.4)の一部として実行する。

Javaの実装は,準備中に次のエラーを検出しなければならない。


このようなエラーを検出した場合は,AbstractMethodErrorのインスタンスを,Javaプログラム内のそのクラスの準備を引き起こした時点で投げなければならない。

Java仮想計算機の実装によっては,以降のクラス又はインタフェースの演算をより効率的にするために,準備時に追加のデータ構造を事前に処理してもよい。特に有用なデータ構造は,Wメソッド表W又はスーパクラスの検索を必要とせずにクラスのインスタンス上でメソッドを呼出し可能とする,それ以外のデータ構造とする。

12.3.3 名前参照の解決

Javaバイナリファイルは,他のクラス及びインタフェースの完全限定名を使用し,他のクラス及びインタフェース,並びにそれらのフィールド,メソッド及びコンストラクタを記号的に参照する(13.1)。 フィールド及びメソッドに対して,これらの記号参照は,そのフィールド又はメソッド自体の名前に加え,そのフィールド又はメソッドを宣言しているクラス又はインタフェース型の名前を適切な型情報とともに含む。

記号参照が使用可能となる前に,まず記号解決(resolution)を行わなければならない。記号解決は,記号参照が正しいことを検査し,さらに,普通は,その参照を繰り返し使用する場合に一層効率的に処理できる直接参照に置換する。

記号解決中にエラーが発生した場合は,エラーを投げる。普通このエラ−は,クラスIncompatibleClassChangeErrorの次に示すサブクラスのうちの一つのインスタンスとするが,クラスIncompatibleClassChangeErrorのこれ以外のサブクラスのインスタンスであってもよいし,クラスIncompatibleClassChangeError自体のインスタンスであってもよい。 このエラーは,その型への記号参照を直接又は間接的に使用するプログラム内の任意の時点で投げてよい。



さらに,あるクラスでnativeメソッドを宣言しており,その実装が見つからない場合,UnsatisfiedLinkErrorLinkageErrorのサブクラス)を投げてもよい。 このエラーは,仮想計算機が使用している記号解決の方法の種類に応じて,メソッドを使用したとき又はそれ以前に発生する(12.3)

12.3.4 リンキング: コード生成との関係

特殊な(非標準の)バイナリ形式(13.1)を使用する実装では,ある型のグループ内の記号参照を,そのグループをロードする前に記号解決してもよい(12.2.2)。 これは,従来の“リンケージ編集”に対応している。リンケージ編集を行わなくとも,Javaの実装には高い柔軟性をもつ。ある型からのすべての記号参照を,その型についての最初のリンケ−ジ処理の時点で記号解決してもよいし,又はそれぞれの記号参照の解決を,その参照を最初に使用する時点まで遅らせてもよい。

Javaの実装に許しているリンケージ処理の柔軟性は,リンケージエラーが発生しない正しい形式のJavaプログラムには影響しないことに注意すること。

12.4 クラス及びインタフェースの初期化

クラスの初期化は,そのクラスで宣言している静的初期化子及びstaticフィールド(クラス変数)の初期化子の実行で構成される。インタフェースの初期化は,そのインタフェースで宣言しているフィールド(定数)の初期化子の実行で構成される。

クラスを初期化する前に,そのスーパクラスを初期化しなければならないが,そのクラスによって実装するインタフェースを初期化する必要はない。同様に,インタフェースを初期化する前に,そのインタフェースのス−パインタフェースを初期化する必要はない。

12.4.1 初期化の発生時点

クラス又はインタフェース型Tは,最初に能動的使用(active use)をしたときに初期化(initialized)する。これは,次による。


型のその他のすべての使用は,受動的使用(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は能動的使用ではない。

12.4.2 初期化手続きの詳細

Javaはマルチスレッドであるため,ある他のスレッドが,同時に同じクラス又はインタフェースの初期化を試みる場合があるので,クラス又はインタフェースの初期化には注意深い同期化を必要とする。 あるクラス又はインタフェースの初期化は,そのクラス又はインタフェースの初期化の一部として再帰的に要求される可能性もある。 例えば,クラスA内の変数初期化子は,無関係のクラスB内のメソッドを呼び出す場合があり,これが,結果としてクラスAのメソッドを呼び出す可能性がある。 Java仮想計算機の実装は,次の手順を使用することによって,同期及び再帰的な初期化を実行する責任がある。 ここで,そのオブジェクトClassは既に検証及び準備済みであること,並びにそのオブジェクトClassは,次の四つの状態の一つを示す状態を含むことを仮定する。



クラス又はインタフェースの初期化の手順は次のとおりとする。



  1. 初期化すべきクラス又はインタフェースを表すオブジェクトClassを同期(14.17)する。これは,実行中のスレッドが,そのオブジェクトに対するロックを獲得できるまで待つことを含む(17.13)

  2. ある他のスレッドによってそのクラス又はインタフェースの初期化が進行中の場合,このオブジェクトClass上でwait(20.1.6)する(一時的にそのロックを解除する)。現在のスレッドのwaitが解放すると,この段階を繰り返す。

  3. 現在のスレッドによってそのクラス又は,インタフェースに対する初期化が進行中の場合,これは,初期化に対する再帰的な要求でなければならない。そのオブジェクトClassのロックを解除し,正常に終了する。

  4. そのクラス又はインタフェースを既に初期化している場合,それ以上の動作は要求しない。そのオブジェクトClassのロックを解除し,正常に終了する。

  5. そのオブジェクトClassがエラー状態にある場合,初期化は不可能とする。そのオブジェクトClassのロックを解除し,NoClassDefFoundErrorを投げる。

  6. そうでなければ,そのオブジェクトClassの初期化が現在のスレッドで進行中であるという事実を記録し,そのオブジェクトClassのロックを解除する。

  7. 次に,そのオブジェクトClassが,インタフェースでなくクラスを表し,このクラスのスーパクラスを未だ初期化していない場合,そのスーパクラスにこの全体の手順を繰り返し実行する。 必要な場合は,最初にそのスーパクラスを検証及び準備する。スーパクラスの初期化が例外を投げることによって中途完了した場合,そのオブジェクトClassをロックし,それにエラーのラベル表示を行い,すべての待機しているスレッド(20.1.10)にそれを通知し,ロックを解除し,それからそのスーパクラスの初期化の結果と同じ例外を投げる。

  8. 次に,クラス変数初期化子及びクラスの静的初期化子,又はインタフェースのフィールド初期化子のいづれかを,記述してある順序で,あたかもそれらが単一ブロックであるかのように実行する。 ただし,それらの値がコンパイル時の定数であるfinalクラス変数及びインタフェースのフィールドの値を,最初に初期化することは除外する(8.3.2.1, 9.3.1,13.4.8)。

  9. 初期化子の実行が正常に完了した場合,そのオブジェクトClassをロックし,それを完全に初期化したことを表示し,すべての待機しているスレッドにそれを通知し(20.1.10),ロックを解除し,それから正常にこの手順を終了する。

  10. そうでなければ,初期化子は,ある例外Eを投げることによって中途完了しなければならない。EのクラスがError又はそのサブクラスの一つでない場合,クラスExceptionInInitializerErrorの新しいインスタンスを,実引数としてEを伴って生成し,それから次の段階のEの所でこのオブジェクトを使用する。 しかし,OutOfMemoryErrorが発生したために,ExceptionInInitializerErrorの新しいインスタンスが生成できない場合,その代わりに,次の段階のEの所でオブジェクトOutOfMemoryErrorを使用する。

  11. そのオブジェクトClassをロックし,エラー表示をし,すべての待機しているスレッドにそれを通知し(20.1.10),ロックを解除し,それから理由E又は以前の段階で定義した代替の理由によって,この手順を中途完了する。



12.4.3 初期化:コード生成との関係


コード生成は,クラス又はインタフェースの初期化可能な場所を,前述した初期化手続きの呼出しを挿入することによって,保持する必要がある。 この初期化手続きが正常に完了し,オブジェクトClassを完全に初期化し使用準備ができれば,初期化手続きの呼出しは不要となる。たとえばパッチによってそれを解除したり,コードを再生成したりすることによって,コードから削除することができる。

関連する型のグループに対する初期化の順序が決定できれば,ある場合には,コンパイル時の解析によって,ある型が生成したコードによって初期化済みであるかどうかの多くの検査を除外することができる。 ただし,それらの解析は,Java言語が並行処理であって,初期化コードが非制限という事実を十分に考慮する必要がある。

12.5 新しいクラスインスタンスの生成

新しいクラスインスタンスは,次の状況のいづれかが発生したときに,明示的に生成する。




新しいクラスインスタンスは,次の状況で暗黙的に生成してもよい。


これらの状況のそれぞれは,クラスインスタンス生成プロセスの一部として,指定した実引数(無い場合もある)を伴って呼び出すべき特定のコンストラクタを識別する。

新しいクラスインスタンスを生成するときには,隠されたすべてのインスタンス変数を含む,そのクラス型で宣言してあるすべてのインスタンス変数及びそのクラス型のそれぞれのスーパクラスに宣言してあるすべてのインスタンス変数に対して,十分な大きさのメモリ空間を割り当てる。 そのオブジェクトにメモリを割り当てるのに十分な領域がない場合,クラスインスタンスの生成は,OutOfMemoryErrorで中途完了する。 そうでなければ,その新しいオブジェクトのすべてのインスタンス変数は,スーパクラスで宣言したものを含み,それらのデフォルト値に初期化される(4.5.4)。 新しく生成したオブジェクトへの参照を結果として返却する直前に,新しいオブジェクトを初期化するために,指定したコンストラクタを,次の手順を使用して実行する。



  1. このコンストラクタ起動のために新しく生成した仮引数変数を,コンストラクタに対する実引数として代入する。
  2. このコンストラクタが,同じクラスの他のコンストラクタの明示的なコンストラクタ呼出しで始まる場合(thisを使用した),実引数の評価及びそのコンストラクタ呼出しを,これらの同じ五つの手順を使用して,反復的に処理する。 そのコンストラクタの呼出しが中途完了した場合,この手順は,同じ理由で中途完了する。そうでなければ,手順5に継続する。
  3. このコンストラクタが,同じクラスの他のコンストラクタの明示的なコンストラクタ呼出しで始まらない場合(thisを使用した)。このコンストラクタが,Object以外のクラスに対するものである場合,このコンストラクタは,スーパクラスのコンストラクタの明示的又は暗黙的な呼出し(superを使用した)で始まる。 実引数の評価及びそのコンストラクタ呼出しを,これらの同じ五つの手順を使用して,反復的に処理する。そのコンストラクタの呼出しが中途完了した場合,この手順は,同じ理由で中途完了する。そうでなければ,手順4に継続する。

  4. そのクラスのソースコード内で,字句的に出現する左から右の順序で,それらの値を対応するインスタンス変数に代入することによって,このクラスに対するインスタンス変数の初期化子を実行する。 これらの初期化子の実行が例外となった場合,以降の初期化子は処理しないで,この手順は,同じ例外で中途完了する。 そうでなければ,手順5に継続する(ある初期のJava実装では,そのフィールドの初期化子の式が,その型に対するデフォルトの初期値と等しい値をもつ定数式であった場合,コンパイラがそのフィールドを初期化するためのコードを不当に省略していた)。
  5. このコンストラクタの残りの本体を実行する。その実行が中途完了した場合,この手順は,同じ理由で中途完了する。 そうでなければ,この手順は正常に終了する。



次に例を示す。


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の新しいインスタンスを生成する。フィールドxy及び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の本体を実行し,x1に,y1に設定する。

次に,クラス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の呼出しが,クラスSuperprintThreeの定義を呼び出さず,むしろクラスTest内で上書きを定義しているprintThreeを呼び出すことを示している。 したがって,このメソッドは,Testのフィールド初期化子を実行する前に動作する。これが,出力の最初の値が0となる理由である。これは,Testのフィールドthreeを初期化したデフォルト値である。 メソッドmain内のprintThreeの後の呼出しは,printThreeの同じ定義を呼び出すが,その時点までには,インスタンス変数threeに対する初期化子を実行している。そのため値3を出力する。 コンストラクタ宣言の詳細については,8.6を参照のこと。

12.6 Classインスタンスの終了

クラス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で規定したように,より効率的にそれらを終了することを推奨する。

終了化子は,他のメソッドと同じように明示的に呼び出してもよい。

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は,それらがあらゆる場合に動作できるように,その設計は簡潔さを保っており,防衛的にプログラムするのがよい。

12.6.2 終了化子の呼出しの非順序性

Javaは,メソッドfinalizeの呼出しについては,順序性を強制しない。終了化子は,任意の順序で呼び出してもよいし,同時に呼び出してもよい。

例えば,非終了化オブジェクトの循環的にリンクされたグループが到達不能(又は終了化子到達可能)になった場合,すべてのオブジェクトをまとめて終了化可能にしてもよい。 最終的には,これらのオブジェクトの終了化子は,任意の順序で呼び出してもよいし,マルチスレッドを使用して同時に呼び出してもよい。 自動記憶域管理が,後に,そのオブジェクトが到達不能であることを検出すると,それらの記憶域は再利用できる。
すべてのオブジェクトが到達不能になったとき.あるオブジェクトの集合に対して,指定した順序で終了化子的なメソッドの集合の呼出しを引き起こすような,Javaクラスを実装することは簡単である。

12.7 クラスの終了

あるクラスが,実引数を取らず,結果を返さない次のクラスメソッドclassFinalizeを宣言した場合を考える。


static void classFinalize() throws Throwable { . . . } 



このメソッドは,クラスをアンロードする前に呼び出される(12.8)。 オブジェクトに対するメソッドfinalizeと同様に,このメソッドは,自動的に一度だけ呼び出される。 このメソッドは,任意に privateprotected又は publicで宣言してもよい。

12.8 クラス及びインタフェースのアンローディング

Java 仮想計算機は,クラスをアンロード(unloaded)する機構を提供してもよい。そのような機構の詳細は,この版のJava言語規程では規定しない。一般的に,関連するクラス及びインタフェース型のグループを,まとめてアンロードする。 例えば,これは,特定のクラスローダを使用することによってロードした,関連する型のグループをアンロードするために使用できる。そのようなグループは,そのすべてのクラスが,例えば,HotJavaのようなJavaベースのブラウザの単一アプレットを実装するクラスから構成されるかもしれない。

クラスは,そのいずれかのインスタンスが到達可能である間は,アンロードしてはならない(12.6)。 クラス又はインタフェースは,それを表すオブジェクトClassが到達可能である間は,アンロードしてはならない。

クラスfainalizer(12.7) を宣言するクラスは,それらをアンロードする前にこれらのfinalizerを実行する。

12.9 仮想計算機の抜出し


Java 仮想計算機は,次の二つのうちのいづれかが発生したとき,そのすべての活動を終了し,抜け出す(exits)



Javaプログラムは,終了化子をもつすべてのオブジェクト及びクラスfinalizerをもつすべてのクラスの終了化子のうち,まだ自動的に呼び出していない終了化子を,仮想計算機を抜け出す前に実行することを指定できる。 これは,クラスSystemのメソッドrunFinalizersOnExitを,実引数trueを指定して呼び出すことによって実行する。 デフォルトは,抜出し時に終了化子を実行しないこととする。この振舞いは,runFinalizersOnExitを実引数falseを指定して呼び出すことによって回復できる。 メソッドrunFinalizersOnExitの呼出しは,呼出し元がexitできる場合にだけ許可され,そうでなければSecurityManager(20.17)によって拒否される。


目次 | |