如果Java finalize方法中存在无限循环或死锁,那么Finalizer线程会做什么

时间:2013-08-04 10:56:31

标签: java multithreading finalizer finalize

如果 Java finalize方法中存在无限循环或死锁终结器线程会怎样做。

3 个答案:

答案 0 :(得分:15)

规范写道:

  

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

     

Java编程语言没有指定调用终结器的时间,除非说它将在重用对象的存储之前发生。

我读到这意味着终结者必须在存储可以重复使用之前完成。

  

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

     

重要的是要注意许多终结器线程可能是活动的(有时在大型共享内存多处理器上需要),并且如果大型连接数据结构变为垃圾,则每个对象的所有最终化方法都是可以同时调用该数据结构,每个终结器调用在不同的线程中运行。

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

不允许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)

答案 1 :(得分:4)

我想说,既然Java规范没有说明必须如何调用finalize方法(只是必须在对象被垃圾收集之前调用它),那么行为是特定于实现的。

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

  

重要的是要注意许多终结器线程可能是活动的   (有时在大型共享内存多处理器上需要这样做)   如果一个大的连接数据结构变成垃圾,所有的   最终确定该数据结构中每个对象的方法   同时调用,每个终结器调用在一个中运行   不同的主题。

查看JDK7的源代码,FinalizerThread保持计划完成的对象队列(实际上对象由GC添加到队列中,当证明无法访问时)检查ReferenceQueue doc ):

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 finalize方法中存在无限循环或死锁,Finalizer线程会做什么。

它只会坐在那里并运行无限循环直到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);

    }
}

注意&#34; Finalize叫&#34;如果在JDK6和JDK7上只打印一次。

答案 2 :(得分:0)

对象不会被释放&#34;,即不会从它们中回收内存,并且在finalize方法中释放的资源将始终保留。

基本上有一个队列包含所有等待执行finalize()方法的对象。 Finalizer线程从此队列中获取对象 - 运行finalize - 并释放该对象。

如果此线程将死锁,则ReferenceQueue队列将会增长,并且在某些时候OOM错误将变得无法阻止。此资源也将被此队列中的对象占用。希望这会有所帮助!!

for(;;)
{
  Finalizer f = java.lang.ref.Finalizer.ReferenceQueue.remove();
  f.get().finalize();
}