Java ファイナライズ メソッドで無限ループまたはデッドロックが発生した場合、ファイナライザ スレッドはどうなりますか。
3 に答える
仕様は次のように書いています。
オブジェクトのストレージがガベージ コレクターによって再利用される前に、Java 仮想マシンはそのオブジェクトのファイナライザーを呼び出します。
Java プログラミング言語では、オブジェクトのストレージが再利用される前にファイナライザーが呼び出されることを除いて、ファイナライザーが呼び出されるまでの時間を指定しません。
これは、ストレージが再利用される前にファイナライザーが完了している必要があることを意味します。
Java プログラミング言語は、特定のオブジェクトのファイナライザーを呼び出すスレッドを指定しません。
多くのファイナライザ スレッドがアクティブになる可能性があることに注意することが重要です (これは、大規模な共有メモリ マルチプロセッサで必要になる場合があります)。また、接続された大規模なデータ構造がガベージになると、そのデータ構造内のすべてのオブジェクトのすべてのファイナライズ メソッドが呼び出される可能性があることに注意してください。同時に、各ファイナライザー呼び出しは異なるスレッドで実行されます。
つまり、ファイナライズはガベージ コレクター スレッド、別のスレッド、または別のスレッド プールで発生する可能性があります。
JVM はファイナライザーの実行を単純に中止することはできず、限られた数のスレッドしか使用できません (スレッドはオペレーティング システムのリソースであり、オペレーティング システムは任意の数のスレッドをサポートしていません)。したがって、終了しないファイナライザは必然的にそのスレッド プールを枯渇させ、それによってファイナライズ可能なオブジェクトのコレクションを禁止し、メモリ リークを引き起こします。
次のテスト プログラムは、この動作を確認します。
public class Test {
byte[] memoryHog = new byte[1024 * 1024];
@Override
protected void finalize() throws Throwable {
System.out.println("Finalizing " + this + " in thread " + Thread.currentThread());
for (;;);
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Test();
}
}
}
Oracle JDK 7 では、次のように表示されます。
Finalizing tools.Test@1f1fba0 in thread Thread[Finalizer,8,system] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at tools.Test.<init>(Test.java:5) at tools.Test.main(Test.java:15)
Java 仕様では finalize メソッドを呼び出す方法が指定されていないため (オブジェクトがガベージ コレクションされる前に呼び出す必要があるというだけです)、動作は実装固有です。
仕様では、複数のスレッドでプロセスを実行することを除外していませんが、必須ではありません。
多くのファイナライザ スレッドがアクティブになる可能性があることに注意することが重要です (これは大規模な共有メモリ マルチプロセッサで必要になる場合があります)。また、接続された大規模なデータ構造がガベージになると、そのデータ構造内のすべてのオブジェクトのすべてのファイナライズ メソッドが呼び出される可能性があることに注意してください。同時に、各ファイナライザー呼び出しは異なるスレッドで実行されます。
JDK7 のソースを見ると、FinalizerThread
ファイナライズのためにスケジュールされたオブジェクトのキューが保持されます (実際には、到達不能であることが判明した場合、オブジェクトは GC によってキューに追加されます -ReferenceQueue
ドキュメントを確認してください)。
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer();
} catch (InterruptedException x) {
continue;
}
}
}
}
各オブジェクトはキューから削除され、runFinalizer
メソッドが実行されます。オブジェクトでファイナライズが実行されたかどうかがチェックされ、そうでない場合は、ネイティブ メソッドの呼び出しとして呼び出されますinvokeFinalizeMethod
。メソッドは単にオブジェクトのメソッドを呼び出してfinalize
います:
JNIEXPORT void JNICALL
Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,
jobject ob)
{
jclass cls;
jmethodID mid;
cls = (*env)->GetObjectClass(env, ob);
if (cls == NULL) return;
mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
if (mid == NULL) return;
(*env)->CallVoidMethod(env, ob, mid);
}
FinalizerThread
これにより、障害のあるオブジェクトで がブロックされている間に、オブジェクトがリストのキューに入れられるという状況が発生し、結果として が発生しOutOfMemoryError
ます。
元の質問に答えるには:
Java ファイナライズ メソッドで無限ループまたはデッドロックが発生した場合、ファイナライザ スレッドはどうなりますか。
単にそこに座って、 まで無限ループを実行しますOutOfMemoryError
。
public class FinalizeLoop {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
for (;;) {
new FinalizeLoop();
}
}
};
thread.setDaemon(true);
thread.start();
while (true);
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Finalize called");
while (true);
}
}
JDK6 および JDK7 で 1 回だけ出力される場合は、「ファイナライズが呼び出されました」に注意してください。