まくまくJavaノート
ファイナライザを避ける
2016-03-17

finalize を実装してはいけない

Java プログラムにおいて、ファイナライザ (finalize メソッドのオーバライド)は次のような理由で推奨されません。

  • オブジェクトが参照されなくなってからファイナライザが呼ばれるまでの時間は不定である。
  • ファイナライザは呼ばれないこともある。
  • System.gcSystem.runFinalization を呼び出してもファイナライザが実行されることは保証されていない。
  • ファイナライザを持つオブジェクトは、持たないオブジェクトに比べ、生成と解放に約 430 倍の時間がかかる (Effective Java)。

オブジェクトの使用が終了したときにリソースを解放しなければいけないケースでは、明示的な終了メソッド(closeterminate など)を呼び出すように実装します。 特にハードウェアリソース(データベースやファイルも含む)を使用するコードは、リソースの占有時間をできるだけ短くするように考慮すべきです。

C++ のデストラクタのように、スコープを外れた時に自動実行するような言語的な仕組みはありませんが、Java 7 からは try-with-resources 構文により、Closable なオブジェクトの close 処理を自動化することができます。

// 煩雑な close 処理を記述しなくてよい
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

Finalizer Guardian イディオム

どうしてもファイナライザを用意する必要がある場合は、ファイナライザガーディアンのイディオムを利用して実装します。

public class MyClass {
    private final Object finalizerGuardian = new Object() {
        @Override
        protected void finalize() throws Throwable {
            // MyClass に関するファイナライズ処理を記述
        }
    }
}

このイディオムを使用することで、MyClass のサブクラスが super.finalize() の呼び出しを忘れた場合にも、正しく MyClass 側のファイナライズ処理が実行されることが保証されます(それでも、ファイナライズ処理自体が実行されないことがあるという問題は解決されません)。

ファイナライザの正当な使い方

Java ではファイナライザに頼らないコーディングを行うことが基本ですが、ファイナライザの正当な使用例として下記の 2 つが Effective Java で紹介されています。

  1. 安全ネットとしての終了メソッド呼び出し
  2. ネイティブピアのリソース解放

まず、1 つ目の「安全ネット」としての使用は、正当な使い方とは言っていますが、これはクライアント側での終了メソッド呼び出しを忘れたとき(不具合)のフェイルセーフ的な使い方です。 本来はクライアント側で正しく終了処理を記述するべきであり、他の組織が作成しているコードで直接手が出せないとか、ブラックボックスになっているといった特殊な事情がない限りは、勧められる使用方法ではありません。

一方で、2 つ目の例で挙げられているように、ネイティブピアのリソースを解放するタイミングを明確にできないようなケースでは、ファイナライザが動作するタイミングで解放せざるを得ないかもしれません。 例えば、Java 側のオブジェクトが初期化に時間のかかるネイティブリソースを参照する場合、そのリソースへの接続をできるだけ維持したいということもあるでしょう。 Java 側のオブジェクトは使用されなくなった段階で GC の解放対象になりますが、そこから参照されていたネイティブリソースは GC では解放されないので、ファイナライザなどを利用して解放してやる必要があります。 いずれにしても、リソースの解放は明示的なタイミングで行い、占有時間を短くするのが基本であり、ファイナライザを使用しなければいけないような設計はできるだけ避けるべきです。

2016-03-17