Lixo Java Thread coletado ou não

85

Esta questão foi postada em algum site. Não encontrei as respostas certas lá, então estou postando aqui novamente.

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

Minha consulta não é sobre interromper um Thread. Deixe-me reformular minha pergunta. A linha A (veja o código acima) inicia um novo Thread; e a linha B torna a referência de thread nula. Portanto, a JVM agora tem um Thread Object (que está em estado de execução) para o qual não existe referência (como t = null na Linha B). Então, minha pergunta é: por que esse thread (que não tem mais referência no thread principal) continua em execução até que o thread principal esteja em execução. No meu entendimento, o objeto thread deveria ter sido coletado como lixo após a Linha B. Tentei executar este código por 5 minutos ou mais, solicitando o Java Runtime para executar GC, mas o thread simplesmente não para.

Espero que o código e a pergunta estejam claros desta vez.

Ashish
fonte

Respostas:

124

Uma thread em execução é considerada uma raiz de coleta de lixo e é uma daquelas coisas que impede que as coisas sejam coletadas. Quando o coletor de lixo determina se o seu objeto é ' alcançável ' ou não, ele sempre está fazendo isso usando o conjunto de raízes do coletor de lixo como pontos de referência.

Considere isso, por que seu thread principal não está sendo coletado como lixo, ninguém está fazendo referência a esse também.

falstro
fonte
17
Esta resposta, como está atualmente, levanta a questão de se os tópicos podem ser GC'ed (depois de encerrados). Uma vez que esta questão está marcada como uma duplicata desta , deve ser mencionado que os encadeamentos não serão mais marcados como "raízes de coleta de lixo" após serem encerrados e, portanto, se tornam acessíveis para GC.
bluenote10
13
A última frase é penetrante: "por que sua linha principal não é lixo ...".
Determinante
então, como acompanhamento, um thread descartado (aquele que foi unido) não será mais considerado uma raiz? Tenho quase certeza de que a resposta é sim, mas estou vendo coisas estranhas em meu aplicativo sob o criador de perfil e isso me fez pensar ...
Groostav
1
Quanto mais respostas leio, mais confuso fico, mas sim, por que o tópico principal não está recebendo lixo coletado é algo para refletir sobre. Mas voltando à questão central, se os threads filhos criassem alguns objetos, não seriam obtidos GC, pois o thread pai ainda está em execução e mantém as referências dos threads filhos mesmo que tenham 'esgotado' (!! ??)
Rahul Kumar
@Groostav Um encadeamento que foi unido não está em execução, portanto, não é uma raiz de coleta de lixo. Mas quando o encadeamento pai foi chamado child.join(), ele tinha uma referência a child. Se essa referência (e qualquer outra) não for descartada, o thread filho não pode ser GCed.
Blaisorblade
23

Como explicado, os threads em execução são, por definição, imunes ao GC. O GC começa seu trabalho examinando as "raízes", que são consideradas sempre acessíveis; raízes incluem variáveis ​​globais ("campos estáticos" no Java-talk) e as pilhas de todos os encadeamentos em execução (pode-se imaginar que a pilha de um encadeamento em execução faz referência à Threadinstância correspondente ).

No entanto, você pode transformar um thread em um thread de "daemon" (consulte Recursos Thread.setDaemon(boolean)). Um encadeamento daemon não é mais coletado como lixo do que um encadeamento não daemon, mas a JVM é encerrada quando todos os encadeamentos em execução são daemon. Uma maneira de imaginar isso é que cada thread, quando termina, verifica se ainda existem threads em execução que não sejam daemon; caso contrário, o encadeamento de encerramento força uma System.exit()chamada, que sai da JVM (eliminando os encadeamentos do daemon em execução). Este não é um problema relacionado ao GC; de certa forma, os threads são alocados manualmente. No entanto, é assim que a JVM pode tolerar encadeamentos semi-desonestos. Isso normalmente é usado para Timerinstâncias.

Thomas Pornin
fonte
19

A JVM tem uma referência a todos os encadeamentos em execução.

Nenhum thread (ou as coisas a que ele se refere) será coletado como lixo enquanto ainda estiver em execução.

Thilo
fonte
13

O Thread não é coletado como lixo porque há referências aos threads que você não pode ver. Por exemplo, existem referências no sistema de tempo de execução.

Quando o Thread é criado, ele é adicionado ao grupo de threads atual. Você pode obter uma lista de Threads no grupo de threads atual, portanto, essa é outra maneira de obter uma referência a ele.

vy32
fonte