2013-08-04 15 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(); 
}