2013-08-04 11 views
12

co będzie nitka Finalizer zrobić, jeśli istnieje nieskończonej pętli lub impasu w Java sfinalizować metodę.co będzie nitka Finalizer zrobić, jeśli istnieje nieskończona pętla lub impasu w Javie sfinalizować metoda

+4

Czy możesz podzielić się z nami jakimkolwiek zachowaniem zaobserwowanym podczas próby? –

+0

Trudno to zaobserwować, JVM nie gwarantuje nawet, że "finalizacja" zostanie wywołana, igły mówią, że nie wiesz, kiedy ma się nazywać – morgano

+0

@morgano Wystarczy prosty "println". Grałem z tym dawno temu, 'sfinalizuj' jest odbierany dość szybko. Są rzadkie przypadki, gdy niektóre nieosiągalne obiekty nie zostaną sfinalizowane do czasu zamknięcia systemu. –

Odpowiedz

13

Spec pisze:

Przed przechowywania obiektu jest odzyskiwana przez śmieciarza, Java Virtual Machine wywoła finalizatora tego obiektu.

Język programowania Java nie określa, jak szybko zostanie wywołany finalizator, z wyjątkiem tego, że powie, że stanie się to przed ponownym użyciem obiektu.

Czytam to w ten sposób, że finalizator musi zostać ukończony, zanim będzie można ponownie wykorzystać miejsce.

Język programowania Java nie określa, który wątek będzie wywoływał finalizator dla dowolnego obiektu.

Należy pamiętać, że wiele wątków finalizatora może być aktywnych (czasami jest to konieczne w przypadku dużych procesorów wieloprocesorowych), a jeśli duża połączona struktura danych staje się śmieciem, wszystkie finalizują metody dla każdego obiektu w tym obszarze. struktura danych może być wywołana w tym samym czasie, każde wywołanie finalizatora działa w innym wątku.

Oznacza to, że finalizacja może wystąpić w wątku odśmiecnika, w osobnej powłoce, lub nawet osobnej puli wątków.

JVM nie może po prostu przerwać wykonywania finalizatora i może używać tylko skończonej liczby wątków (wątki są zasobami systemu operacyjnego, a systemy operacyjne nie obsługują arbitralnie wielu wątków). Nie kończące się finalizatory będą zatem musiały zagłodzić tę pulę wątków, tym samym uniemożliwiając zbieranie dowolnych obiektów ulegających sfinalizowaniu i powodując wyciek pamięci.

Poniższy program testowy potwierdza to zachowanie:

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, Drukuje:

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, wykonał mniej więcej ten sam test i uzyskał taki sam wynik. W grę wchodzi tylko jeden wątek finalizatora. –

+0

@mriton, co oznacza znaczenie ** _ Ważne jest, aby pamiętać, że wiele wątków finalizatora może być aktywnych (czasami jest to potrzebne w dużych multiprocesorach z pamięcią wspólną) _ **. Czy ma jakieś połączenie z wybranym algorytmem GC, takim jak CMS. I czy istnieje związek między liczbą nici Finalizer a algorytmem GC. – andy

+1

JVM może to zrobić, jak chce, a różne maszyny JVM mogą robić to inaczej. Jeśli interesuje Cię konkretna maszyna JVM, sprawdź jej dokumentację (lub, co bardziej prawdopodobne, jej kod źródłowy). Anwerke lpiepiora wydaje się wskazywać, że w JVM Oracle zawsze istnieje jeden wątek finalizatora, niezależnie od używanego algorytmu zbierania śmieci. – meriton

4

Powiedziałbym, że skoro Specyfikacja Java nie powiedzieć jak należy wywołać metodę finalize (wystarczy, że trzeba ją wywołać, zanim obiekt zostanie zebrany), zachowanie jest specyficzne dla implementacji.

Spec nie wyklucza konieczności wielu wątków uruchomiony proces, ale nie wymaga go:

Ważne jest, aby pamiętać, że wiele wątków finalizatora może być aktywny (czasami jest to konieczne na dużych wieloprocesorowych pamięci współużytkowanych) i , że jeśli duża połączona struktura danych stanie się śmieciem, wszystkie ostateczne metody dla każdego obiektu w tej strukturze danych mogą być wywoływane w tym samym czasie, każde wywołanie finalizatora działa w innym wątku niż .

Patrząc na źródeł JDK7 The FinalizerThread utrzymuje kolejkę obiektów planowanych do finalizacji (faktycznie obiekty są dodawane do kolejki przez GC, kiedy okazał się nieosiągalny - sprawdź 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; 
      } 
     } 
    } 
} 

Każdy obiekt jest usuwany z kolejki i uruchamiana jest na nim metoda runFinalizer. Sprawdzanie jest wykonywane, jeśli finalizacja została uruchomiona na obiekcie, a jeśli nie jest wywoływana, jako wywołanie metody natywnej invokeFinalizeMethod. Metoda prostu jest wywołanie metody finalize na obiekcie:

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

Powinno to doprowadzić do sytuacji, w której przedmioty dostać w kolejce na liście, podczas gdy FinalizerThread jest zablokowany na wadliwego przedmiotu, co z kolei powinno doprowadzić do OutOfMemoryError.

Więc odpowiedzieć na oryginalne pytanie:

co będzie nitka Finalizer zrobić, jeśli istnieje nieskończona pętla lub impasu w Javie sfinalizować metody.

Po prostu usiądzie tam i uruchomi tę nieskończoną pętlę aż do 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); 

    } 
} 

Uwaga "Zakończono wywołanie", jeśli jest drukowane tylko jeden raz w JDK6 i JDK7.

+0

Niestety, nie zgadzam się z powyższym przykładowym kodem. pętla for zakończy się w tym samym czasie co główny wątek, więc ** nie może ** zobaczyć żadnej rzeczy. – andy

+0

@ Jeśli masz rację, zmieniłem kod nieco, aby upewnić się, że coś zobaczysz w końcu - w zależności od tego, kiedy Twój GC się pojawi. Dzięki! – lpiepiora

0

Obiekty nie zostaną "uwolnione", to znaczy, że pamięć nie zostanie odebrana od nich, a zasoby uwolnione w metodzie finalizacji pozostaną zarezerwowane przez cały czas.

Zasadniczo istnieje kolejka zawierająca wszystkie obiekty czekające na wykonanie metody finalize(). Wątek finalizera odbiera obiekty z tej kolejki - uruchamia finalizację - i zwalnia obiekt.

Jeśli ten wątek będzie zakleszczony, kolejka ReferenceQueue będzie rósł iw pewnym momencie błąd OOM stanie się nieubłagany. Również zasoby zostaną podzielone przez obiekty w tej kolejce. Mam nadzieję że to pomoże!!

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