はじめに
Javaのファイナライズ機能は、ガベージコレクタが到達不能と判断したオブジェクトに対して事後クリーンアップを実行するための仕組みです。通常は、オブジェクトに関連付けられたネイティブリソースを再生(reclaim)する場合に使います。簡単なファイナライズの例を次に示します。
public class Image1 { // pointer to the native image data private int nativeImg; private Point pos; private Dimension dim; // it disposes of the native image; // successive calls to it will be ignored private native void disposeNative(); public void dispose() { disposeNative(); } protected void finalize() { dispose(); } static private Image1 randomImg; }
Image1
のインスタンスが到達不能になると、Java仮想マシン(JVM)は状況に応じて、画像データ(この例では整数型のnativeImg
で参照)を保持しているネイティブリソースを確実に再生するためにImage1
のfinalize()
メソッドを呼び出します。ここで注意してほしいのは、finalize()
メソッドはJVMによって特殊な扱いを受けるものの、任意のコードからなる任意のメソッドだという点です。具体的には、finalize()
メソッドはオブジェクト内のすべてのフィールド(この例ではpos
とdim
)にアクセスできます。また、驚くべきことに、finalize()
メソッドの中でオブジェクトを再び到達可能にすることもできます(たとえばrandomImg = this;
として静的フィールドから到達可能にするなど)。このプログラミング手法はあまりお勧めしませんが、方法としては可能です。
ここで、ファイナライズ可能なオブジェクト(クラスに有効なファイナライザが定義されているオブジェクト)が作成されてから回収されるまでの流れを見てみましょう。オブジェクトの名前はobj
とします(図1を参照)。
obj
が割り当てられるときに、JVMはobj
がファイナライズ可能であることを内部に記録します(通常はこれによってJVMの高速割り当てパスの速度が低下します)。- ガベージコレクタが
obj
を到達不能と判断します。このとき、ガベージコレクタは割り当て時の記録からobj
がファイナライズ可能であることを認識し、このオブジェクトをJVMのファイナライズキューに追加します。また、その時点で到達不能になっているオブジェクトでも、それがobj
から到達可能なオブジェクトであれば、ファイナライザからアクセスされる可能性があるのですべて保持します。図2に、Image1
インスタンスの場合の例を示します。 - その後、JVMのファイナライザスレッドが
obj
をキューから取り出し、obj
のfinalize()
メソッドを呼び出して、ファイナライザが呼び出されたことを記録します。この時点で、obj
は「ファイナライズ済み」と見なされます。 - ガベージコレクタが再び
obj
を到達不能と判断しますが、今度はファイナライズ済みなので、obj
のスペースと、obj
オブジェクトから到達可能なすべてのオブジェクト(ただし他から到達不能なもの)を回収します。
ガベージコレクタがobj
を回収するのに最低でも2サイクルを必要とし、このプロセス中はobj
から到達可能なすべてのオブジェクトを保持する必要があることに注意してください。この点を忘れていると、一時的に予想外のリソース保持問題が発生する可能性があります。もう1つ注意してほしいのは、JVMは、割り当てられたすべてのファイナライズ可能なオブジェクトのファイナライザを呼び出すとは限らないという点です。ガベージコレクタが一部のオブジェクトを到達不能と見なす前にJVMが終了することもあります。