finalize()の公式ドキュメントには次のように書かれています:
ガベージコレクションがオブジェクトへの参照がこれ以上ないと判断したときに、オブジェクトのガベージコレクタによって呼び出されます。これから、「凍結」ファイナライズ()がガベージコレクタースレッドを中断し、ビルドが停止すると想定できます。 実際には(少なくともHotSpot 1.6では)ガベージコレクターはfinalize()メソッドを直接呼び出しませんが、静的メソッドjava.lang.ref.Finalizer.register(Object)を呼び出すことにより、対応するオブジェクトを特別なリストに追加するだけです。 Finalizerクラスのオブジェクトは、finalize()を呼び出す必要のあるオブジェクトへの参照であり、次および前のFinalizerへのリンクを保存して、二重にリンクされたリストを形成します。
finalize()呼び出しは、個別のストリーム「Finalizer」(java.lang.ref.Finalizer.FinalizerThread)で直接行われます。これは、仮想マシンの起動時に作成されます(より正確には、Finalizerクラスをロードするときの静的セクション)。 finalize()メソッドは、ガベージコレクターによってリストに追加された順序で順番に呼び出されます。 したがって、finalize()がハングすると、Finalizerスレッドはハングしますが、ガベージコレクタはハングしません。 特に、これはfinalize()メソッドを持たないオブジェクトは適切に削除されますが、Finalizerスレッドがハングする、アプリケーションが終了する、またはメモリがなくなるまでキューに追加されるオブジェクトを意味します。
これを例で説明します。 オブジェクトがヒープ上の適切な場所を食べるクラスを作成しましょう:
static class BigObject { char[] tmp = new char[10000]; }
そして、このメインメソッドのようなものを書きます:
public static void main(String... args) { int i=0; while(true) { new BigObject(); try { Thread.sleep(10); } catch( InterruptedException e ) {} if(i++%100==0) System.out.println("Total: "+Runtime.getRuntime().totalMemory()+ "; free: "+Runtime.getRuntime().freeMemory()); } }
各反復でオブジェクトを作成し、100回の反復で残りのメモリに関する情報を表示します。 テストを遅延させないようにJavaマシンのメモリを制限し、結果を確認します。
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14013136
Total: 16252928; free: 12011536
Total: 16252928; free: 14309664
Total: 16252928; free: 12308064
Total: 16252928; free: 14797440
Total: 16252928; free: 12795840
Total: 16252928; free: 15307784
...
メモリは適切に割り当てられ、解放されます。
ここで、オブジェクトが非常に長い間finalize()を実行しているクラスを作成します。
static class LongFinalize { protected void finalize() throws Throwable { System.out.println("LongFinalize finalizer"); Thread.sleep(10000000); } }
ループの前に新しいLongFinalize()をmain()に追加します。 結果は次のようになります。
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14003496
Total: 16252928; free: 12001896
LongFinalize finalizer
Total: 16252928; free: 14290408
Total: 16252928; free: 12288808
Total: 16252928; free: 14777432
Total: 16252928; free: 12775832
Total: 16252928; free: 15286960
Total: 16252928; free: 13280880
ご覧のとおり、LongFinalize.finalize()を呼び出したにもかかわらず、ガベージコレクターは引き続き動作します。 次に、finishize()メソッドをBigObjectオブジェクトに追加します。これは重要ではないことを行います。
static class BigObject { char[] tmp = new char[10000]; protected void finalize() throws Throwable { tmp[0] = 1; } }
今回は写真が異なります:
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14003496
Total: 16252928; free: 12001896
LongFinalize finalizer
Total: 16252928; free: 9996648
Total: 16252928; free: 7987144
Total: 16252928; free: 6459728
Total: 16252928; free: 4458128
Total: 16252928; free: 6357016
Total: 16252928; free: 4347352
Total: 16252928; free: 2331112
Total: 16252928; free: 329512
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Test$BigObject.<init>(Test.java:12)
at Test.main(Test.java:31)
メモリがさらに大きくなると、Finalize()がLongFinalize.finalizer()の前に実行されたBigObjectオブジェクトが破棄されたことに注意してください。
そのような状況では、finalize()を持たないオブジェクトのみが正しく削除されると上記で書きました。 実際、finalize()メソッドが空であれば十分です。 ガベージコレクターは、finalize()本体にコードがある場合にのみオブジェクトをFinalizerキューに追加します。 たとえば、空のfinalize()を持つ子クラスを作成できます。
static class SubBigObject extends BigObject { protected void finalize() throws Throwable { } }
main()で子クラスのオブジェクトを作成します(新しいBigObject()を新しいSubBigObject()に置き換えます)。 ガベージコレクションが再び成功することがわかります。
したがって、空のfinalize()をサブクラス化して子オブジェクトのみを作成すると、オブジェクトの破棄を加速し、凍結されたファイナライザーストリームから保護することもできます。 もちろん、あなたが何をしているのかを知っておく必要があります:finalize()が書かれていれば、おそらく何かのためにそれを必要としているでしょう。 それでも、どうしても必要な場合を除き、finalize()を記述しないでください。 たとえば、抽象クラスInputStreamでは、finalize()を実行して、利便性を高めるためにclose()を呼び出すことができます。 実際、finalize()は、システムリソース(FileInputStreamなど)を直接操作する子クラスでのみ定義されます。 そして、例えば、BufferedInputStreamでは、FileInputStreamをラップする場合でもfinalize()は必要ありません。 ここで、過度の汎用性は有害です。 一部のライブラリの作成者が、抽象クラスで不必要にfinalize()を作成し、システムリソースを操作しない場合は、実装内で空の本体で再定義します。 実際、Finalizerがフリーズしなくても、解放されたオブジェクトの流れに単に対処できない場合があり、そのため、オブジェクトの削除とヒープの成長が大幅に遅くなります。
System.runFinalization()のようなものについても言わなければなりません。 この呼び出しは、2番目のスレッド "SecondaryFinalizer"を作成します。このスレッドは、同じキューからのオブジェクトに対してfinalize()も呼び出します。 同時に、System.runFinalization()を呼び出したスレッドは、現在利用可能なFinalizerキューが終了するまで待機します。 原則として、メインのファイナライザがフリーズした場合、OutOfMemoryからあなたを救うことができます。 SubBigObjectのないプログラムのバージョンに戻り、メモリが十分に残っていない場合にこの呼び出しを追加します。 混乱しないように、全文をお伝えします。
public final class Test { static class LongFinalize { protected void finalize() throws Throwable { System.out.println("LongFinalize finalizer"); Thread.sleep(10000000); } } static class BigObject { char[] tmp = new char[10000]; protected void finalize() throws Throwable { tmp[0] = 1; } } public static void main(String... args) { int i=0; new LongFinalize(); while(true) { new BigObject(); try { Thread.sleep(10); } catch( InterruptedException e ) {} if(i++%100==0) System.out.println("Total: "+Runtime.getRuntime().totalMemory()+ "; free: "+Runtime.getRuntime().freeMemory()); if(Runtime.getRuntime().freeMemory()<1e6) System.runFinalization(); } } }
作業の結果を見てみましょう。
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14003496
Total: 16252928; free: 12001896
LongFinalize finalizer
Total: 16252928; free: 9996648
Total: 16252928; free: 7987144
Total: 16252928; free: 6459832
Total: 16252928; free: 4458232
Total: 16252928; free: 6357120
Total: 16252928; free: 4347456
Total: 16252928; free: 2331216
Total: 16252928; free: 239072
Total: 16252928; free: 11729800
Total: 16252928; free: 9717584
Total: 16252928; free: 7719416
Total: 16252928; free: 5710768
Total: 16252928; free: 3721880
Total: 16252928; free: 1710824
Total: 16252928; free: 11261488
メインのファイナライザー()がハングするという事実にもかかわらず、プログラムは存続します。 もちろん、長いファイナライズ()でキューに多くのオブジェクトがある場合、これはあなたを救いません、そして一般にプログラムのSystem.runFinalization()への明示的な呼び出しは何かが間違っていることを示す可能性が高いです。