Zebrano elementy bezużyteczne wątków Java lub nie

85

To pytanie zostało opublikowane w jakiejś witrynie. Nie znalazłem tam właściwych odpowiedzi, więc zamieszczam je tutaj ponownie.

public class TestThread {
    public static void main(String[] s) {
        // anonymous class extends Thread
        Thread t = new Thread() {
            public void run() {
                // infinite loop
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                    // as long as this line printed out, you know it is alive.
                    System.out.println("thread is running...");
                }
            }
        };
        t.start(); // Line A
        t = null; // Line B
        // no more references for Thread t
        // another infinite loop
        while (true) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
            }
            System.gc();
            System.out.println("Executed System.gc()");
        } // The program will run forever until you use ^C to stop it
    }
}

Moje zapytanie nie dotyczy zatrzymania wątku. Pozwólcie, że przeformułuję moje pytanie. Linia A (patrz kod powyżej) rozpoczyna nowy wątek; i Linia B sprawiają, że odwołanie do wątku jest puste. Tak więc maszyna JVM ma teraz obiekt wątku (który jest uruchomiony), do którego nie istnieje żadne odniesienie (jako t = null w wierszu B). Moje pytanie brzmi więc, dlaczego ten wątek (który nie ma już odniesienia w głównym wątku) działa dalej, dopóki nie zostanie uruchomiony główny wątek. Zgodnie z moim zrozumieniem obiekt wątku powinien zostać usunięty po linii B. Próbowałem uruchomić ten kod przez 5 minut i dłużej, żądając Java Runtime do uruchomienia GC, ale wątek po prostu się nie zatrzymuje.

Mam nadzieję, że tym razem zarówno kod, jak i pytanie są jasne.

Ashish
źródło

Odpowiedzi:

124

Działający wątek jest uważany za tak zwany katalog odśmiecania pamięci i jest jedną z tych rzeczy, które uniemożliwiają zbieranie elementów bezużytecznych. Kiedy moduł odśmiecania pamięci ustala, czy obiekt jest „ osiągalny ”, czy nie, zawsze używa zestawu korzeni modułu odśmiecającego jako punkty odniesienia.

Rozważ to, dlaczego twój główny wątek nie jest zbierany jako śmieci, nikt też nie odwołuje się do tego.

falstro
źródło
17
Ta odpowiedź, w obecnym kształcie, rodzi pytanie, czy wątki mogą w ogóle zostać poddane GC (po ich zakończeniu). Ponieważ to pytanie jest oznaczone jako duplikat tego , należy wspomnieć, że wątki nie będą już oznaczane jako „katalogi odśmiecania pamięci” po zakończeniu, a tym samym staną się osiągalne dla GC.
bluenote10
13
Ostatnie zdanie jest przenikliwe: "dlaczego twój główny wątek nie jest śmieciem ...".
Determinant
tak więc w ramach kontynuacji, czy usunięty wątek (taki, który został połączony) nie będzie już uważany za korzeń? Jestem prawie pewien, że odpowiedź brzmi tak, ale widzę dziwne rzeczy w mojej aplikacji pod profilerem i zastanawiam się ...
Groostav
1
Im więcej odpowiedzi czytam, tym bardziej jestem zdezorientowany, ale tak, dlaczego głównym wątkiem nie jest zbieranie śmieci, jest coś, nad czym warto się zastanowić. Ale wracając do podstawowego pytania, jeśli wątki potomne utworzą jakieś obiekty, nie otrzymają GCed, ponieważ wątek nadrzędny nadal działa i przechowuje odwołania do wątków potomnych, nawet jeśli „skończyły się” (!! ??)
Rahul Kumar
@Groostav Wątek, który został przyłączony, nie jest uruchomiony, więc nie jest katalogiem głównym czyszczenia pamięci. Ale kiedy wywołał wątek nadrzędny child.join(), miał odniesienie do child. Jeśli to odwołanie (i dowolne inne) nie zostanie odrzucone, wątek podrzędny nie może zostać GCed.
Blaisorblade
23

Jak wyjaśniono, działające wątki są z definicji odporne na GC. GC rozpoczyna swoją pracę od skanowania „korzeni”, które uważa się za zawsze osiągalne; korzenie zawierają zmienne globalne („pola statyczne” w języku Java) i stosy wszystkich działających wątków (można sobie wyobrazić, że stos działającego wątku odwołuje się do odpowiedniej Threadinstancji).

Możesz jednak uczynić z wątku wątek „demona” (zobacz Thread.setDaemon(boolean)). Wątek demona nie jest bardziej zbierany jako śmieci niż wątek niebędący demonem, ale JVM kończy działanie, gdy wszystkie uruchomione wątki są demonami. Można sobie wyobrazić, że każdy wątek, po zakończeniu, sprawdza, czy pozostały jakieś wątki, które nie są uruchomione przez demona; jeśli nie, wątek kończący wymusza System.exit()wywołanie, które opuszcza JVM (zabija działające wątki demona). To nie jest kwestia związana z GC; w pewnym sensie wątki są przydzielane ręcznie. Jednak w ten sposób JVM może tolerować częściowo nielegalne wątki. Jest to zwykle używane w Timerprzypadku instancji.

Thomas Pornin
źródło
19

JVM ma odniesienie do wszystkich uruchomionych wątków.

Żaden wątek (ani rzeczy, do których się odnosi) nie zostanie usunięty z pamięci, gdy będzie on nadal działał.

Thilo
źródło
13

Wątek nie jest usuwany bez pamięci, ponieważ istnieją odwołania do wątków, których nie można zobaczyć. Na przykład istnieją odwołania w systemie wykonawczym.

Tworzony wątek jest dodawany do bieżącej grupy wątków. Możesz uzyskać listę wątków w bieżącej grupie wątków, więc jest to inny sposób na uzyskanie odniesienia do niej.

vy32
źródło