Effective Java 第 2 版:第 2 章 オブジェクトの生成と消滅 項目 7
ファイナライザを避ける
Effective Java 第 2 版、第 2 章の項目 7。
ファイナライザはオブジェクトがガベージコレクション (GC) により破棄されるときに呼び出されるメソッドである。
Java では Object
クラスで protected void finalize()
として定義されている(中身は空)。
実際には、finalize
をオーバーライドして使う。
ファイナライザはオブジェクトの持つ資源(メモリ、ファイル)を解放するために使えそうである。 しかし、この項目は、ほとんどの場合でファイナライザは使うべきでないと述べられている。
なぜ使うべきでないか
ファイナライザを使うべきでない理由は以下の通りである。
- ファイナライザがいつ実行されるかわからない
- 不正にファイナライザが終了する危険性がある
- ファイナライザは非常にコストが高い
1 は、ファイナライザが GC に伴うものであることによる。 GC のアルゴリズムが JVM 実装依存であることにより、プログラマの意図したタイミングでファイナライズされない。 また、GC の対象オブジェクトもファイナライズされる保証はない。
2 は、ファイナライズ中に例外が投げられると起きる。 これにより、オブジェクトが不正な状態のままになる可能性がある。 さらに、ファイナライザで投げられた例外はスタックトレースを表示してプログラムを終了させない。
3 については、ファイナライザを持つオブジェクトの生成と解放は、それを持たないオブジェクトの 430 倍遅いということである。
ファイナライザの代替策:明示的終了メソッド
ファイナライザに代わる方法として、明示的終了メソッド (explicit termination method) が提案されている。
例えば terminate
というメソッドの中で資源を後始末する処理を書いておき、以下のように使えばよい。
Hoge obj = new Hoge();
try {
obj.doSomething();
//...
} catch {
obj.terminate(); // ファイナライザの代わりに明示的終了メソッド
}
あるオブジェクトの明示的終了メソッドが呼び出されたあとに、そのオブジェクトが使われないようにする必要がある。
そのためには、private
フィールドにそのオブジェクトが終了済みであることを記録し、終了済みのオブジェクトが利用されれば IllegalStateException
が投げられるようにすればよい。
ファイナライザの存在意義
では、ファイナライズの存在意義は何になるのだろうか。 それは以下の二つである。
- セーフネット(安全ネット)
- ネイティブピアの回収
1 は、明示的終了メソッドを呼び忘れた際のセーフネットとして使えるということである。 このセーフネットが使われたときは、何らかの方法で正しく使われていないことに対する警告を記録しておくべきである。
2 は、JVM 上ではなく CPU 上で動いているネイティブオブジェクトに関係する。 ネイティブピアは、通常のオブジェクトがネイティブメソッドを通して委譲するネイティブオブジェクトのことである。 ネイティブピアは JVM のあずかり知るところではないので、GC で解放されない。 そのため、ネイティブピアが重要な資源を持っていないときはファイナライザで解放するのが適切である。 しかし、即座に解放したい資源をネイティブピアが持っているときは、明示的終了メソッドで解放したほうがよい。
ファイナライザガーディアン
ファイナライザを持つクラスがサブクラスを持つ場合を考える。
このサブクラスで finalize
をオーバーライドすると、その finalize
内で super.finalize
を呼ばないと、スーパークラスはファイナライズされない。
つまり、ファイナライザはサブクラスからスーパークラスへ連鎖的に呼び出しされない。
super.finalize
を呼ぶために、以下のようにすればよい。
@Override protected void finalize() throws Throwable {
try {
// ファイナライズする
} finally {
super.finalize();
}
}
しかし、finally
節を忘れるとスーパークラスがファイナライズされない。
これを防ぐために、ファイナライザガーディアンを使うことができる。
ファイナライザガーディアンは、ファイナライズしたいクラスの内部クラスとして、無名クラスを使い、以下のように書く。
public class Hoge {
private final Object finalizerGurdian = new Object() {
@Override protected void finalize() {
// Hoge をファイナライズする処理
}
};
// ...
}
例えば、Hoge
のサブクラス Fuga
を考える。
Fuga extends Hoge {
@Override protected void finalize() {
// Fuga のファイナライズ処理
// super.finalize は忘れている
// ...
}
Fuga
のインスタンスがどの変数からも参照されない状態になったとき、GC によってファイナライズの対象となるインスタンスは、以下の二つである。
Fuga
のインスタンス- ファイナライザガーディアン
finalizerGurdian
Fuga
のインスタンスがファイナライズされるとき、Fuga
のファイナライザ内で super.finalize
を忘れていたとしても、ガーディアンのファイナライザにより、そのエンクロージングインスタンスである Hoge
のインスタンスがファイナライズされる。