如果 Java finalize 方法中存在无限循环或死锁,终结器线程将如何处理

2022-09-03 06:25:52

如果 Java finalize 方法中存在无限循环或死锁,终结器线程将如何处理。


答案 1

该规范写道:

在垃圾回收器回收对象的存储之前,Java 虚拟机将调用该对象的终结器。

Java 编程语言没有指定调用终结器的速度,只是说在重用对象的存储之前将发生这种情况。

我读到这句话的意思是,在可以重用存储之前,终结器必须已经完成。

Java 编程语言不指定哪个线程将调用任何给定对象的终结器。

请务必注意,许多终结器线程可能处于活动状态(有时在大型共享内存多处理器上需要这样做),并且如果大型连接的数据结构成为垃圾,则可以同时调用该数据结构中每个对象的所有 finalize 方法,每个终结器调用在不同的线程中运行。

也就是说,终结可能发生在垃圾回收器线程中,在单独的线程中,甚至在单独的线程池中。

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)

答案 2

我想说的是,由于Java规范没有说明必须如何调用finize方法(只是在垃圾回收对象之前必须调用它),因此行为是特定于实现的。

该规范不排除有多个线程运行该进程,但不需要它:

请务必注意,许多终结器线程可能处于活动状态(有时在大型共享内存多处理器上需要这样做),并且如果大型连接的数据结构成为垃圾,则可以同时调用该数据结构中每个对象的所有 finalize 方法,每个终结器调用在不同的线程中运行。

查看 JDK7 的源代码,将对象队列安排为完成(实际上,当对象被证明无法访问时,GC 会将对象添加到队列中 - 检查文档):FinalizerThreadReferenceQueue

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;
            }
        }
    }
}

从队列中删除每个对象,并在其上运行方法。检查是否在对象上运行了终结,如果没有,则作为对本机方法的调用调用该对象。该方法只是在对象上调用该方法:runFinalizerinvokeFinalizeMethodfinalize

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);
}

这应该会导致一种情况,即对象在列表中排队,而在有故障的对象上被阻止,这反过来应该会导致 。FinalizerThreadOutOfMemoryError

所以要回答原来的问题:

如果 Java finalize 方法中存在无限循环或死锁,终结器线程将如何处理。

它将简单地坐在那里并运行无限循环,直到。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);

    }
}

请注意“Finalize called”,如果只在 JDK6 和 JDK7 上打印一次。