2013-08-04 14 views
12

如果有一个无限循环会出现什么终结器线程做或的Java死锁 finalize方法如果在Java中的无限循环或僵局究竟会终结器线程做finalize方法

+4

您可以与我们分享您在尝试此操作时观察到的任何行为吗? –

+0

很难观察到这一点,JVM甚至不保证会调用'finalize',针头会说你不知道它什么时候会被叫做 – morgano

+0

@morgano一个简单的'println'就够了。我很久以前玩过这个游戏,“敲定”很快得到调用。只有极少数情况下,在系统关闭之前,某些不可访问的对象没有完成。 –

回答

13

该规范中写道:为对象的存储是由垃圾回收器回收

之前,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(); 
     } 
    } 
} 

在甲骨文JDK 7,这个打印:

Finalizing [email protected] 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

+1,做了或多或少的相同的测试,并得到了相同的结果。只涉及一个终结器线程。 –

+0

@ mriton,** _的含义是什么?注意许多终结器线程可能是活动的(这在大型共享内存多处理器上有时需要)_ **。它与我们选择的GC算法有什么联系,如CMS。而且,Finalizer线程的数量和GC算法之间是否存在关系? – andy

+1

但是JVM可能会这样做,但不同的JVM可能会以不同的方式执行此操作。如果您对特定的JVM感兴趣,请检查其文档(或更可能是其源代码)。 lpipiora的anwer似乎表明Oracle JVM中总是有一个终结器线程,而不管正在使用的垃圾收集算法。 – meriton

4

我要说的是,因为Java规范不告诉如何调用finalize方法(只需在对象被垃圾收集之前调用它),该行为就是特定于实现的。

该规范不排除其运行过程中的多个线程,但并不需要它:

地注意到,许多终结的线程可以是活动是很重要的 (这有时需要在大型共享内存多处理器)和 ,如果一个大的连接数据结构变成垃圾,那么所有的数据结构中的每个对象的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方法。

它会简单地坐在那里并运行该无限循环,直到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只打印一次。

+0

对不起,我不同意你上面的示例代码。 for循环会在主线程同时终止,所以你**不会**看到任何东西。 – andy

+0

@andy你是对的,我已经稍微改变了代码,以确保你最终会看到什么 - 我想取决于你的GC何时开始。谢谢! – lpiepiora

0

对象不会被“释放”,也就是说内存将不会被回收,而且在finalize方法中释放的资源将始终保留。

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

如果此线程死锁,则ReferenceQueue队列将长大,并且在某些时候,OOM错误将变得不可避免。此外,资源将被此队列中的对象占用。希望这可以帮助!!

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