A atribuição de objetos a nulos em Java impacta a coleta de lixo?

85

A atribuição de uma referência de objeto não utilizado nullem Java melhora o processo de coleta de lixo de alguma forma mensurável?

Minha experiência com Java (e C #) me ensinou que muitas vezes é contra-intuitivo tentar superar a máquina virtual ou o compilador JIT, mas já vi colegas de trabalho usarem esse método e estou curioso para saber se essa é uma boa prática a escolher ou uma daquelas superstições de programação vodu?

James McMahon
fonte

Respostas:

66

Normalmente, não.

Mas como todas as coisas: depende. O GC em Java atualmente é MUITO bom e tudo deve ser limpo logo depois que não estiver mais acessível. Isso ocorre logo após deixar um método para variáveis ​​locais e quando uma instância de classe não é mais referenciada para campos.

Você só precisa anular explicitamente se souber que permaneceria referenciado de outra forma. Por exemplo, uma matriz que é mantida. Você pode anular os elementos individuais da matriz quando eles não forem mais necessários.

Por exemplo, este código de ArrayList:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}

Além disso, anular explicitamente um objeto não fará com que um objeto seja coletado antes do que se ele simplesmente saísse do escopo naturalmente, desde que nenhuma referência permanecesse.

Ambos:

void foo() {
   Object o = new Object();
   /// do stuff with o
}

e:

void foo() {
   Object o = new Object();
   /// do stuff with o
   o = null;
}

São funcionalmente equivalentes.

Mark Renouf
fonte
7
você também pode querer anular referências dentro de métodos que podem usar uma grande quantidade de memória ou levar muito tempo para serem executados, ou no código em um aplicativo Swing que pode ser executado por muito tempo. Em métodos curtos ou objetos de curta duração, não vale a pena o código extra confuso.
John Gardner
1
Então, está certo, quando anulamos um objeto, o Garbage Collector coleta esse objeto imediatamente?
Muhammad Babar
1
Não. O coletor de lixo é executado periodicamente para ver se havia algum objeto para o qual nenhuma variável de referência está apontando. Quando você os define como nulos e os chama System.gc(), eles são removidos (desde que não haja outras referências apontando para esse objeto).
JavaTechnical
2
@JavaTechnical Como eu sei, não é garantido que, chamando System.gc (), a coleta de lixo seja iniciada.
Jack
12

Na minha experiência, na maioria das vezes, as pessoas anulam as referências por paranóia, não por necessidade. Aqui está uma orientação rápida:

  1. Se o objeto A fizer referência ao objeto B e você não precisar mais dessa referência e o objeto A não for elegível para coleta de lixo, você deve anular explicitamente o campo. Não há necessidade de anular um campo se o objeto delimitador estiver recebendo a coleta de lixo de qualquer maneira. Anular campos em um método dispose () é quase sempre inútil.

  2. Não há necessidade de anular as referências de objeto criadas em um método. Eles serão apagados automaticamente assim que o método terminar. A exceção a essa regra é se você estiver executando um método muito longo ou algum loop massivo e precisar garantir que algumas referências sejam apagadas antes do final do método. Novamente, esses casos são extremamente raros.

Eu diria que na grande maioria das vezes você não precisará anular as referências. Tentar ser mais esperto que o coletor de lixo é inútil. Você acabará com um código ineficiente e ilegível.

Gili
fonte
11

Um bom artigo é o horror da codificação de hoje .

A forma como o GC trabalha é procurando por objetos que não tenham nenhum indicador para eles, a área de sua pesquisa é heap / stack e quaisquer outros espaços que eles tenham. Portanto, se você definir uma variável como nula, o objeto real agora não é apontado por ninguém e, portanto, pode ser GC.

Mas como o GC pode não funcionar naquele exato instante, você pode não estar comprando nada. Mas se o seu método for bastante longo (em termos de tempo de execução), pode valer a pena, pois você estará aumentando suas chances de GC coletar esse objeto.

O problema também pode ser complicado com otimizações de código, se você nunca usar a variável depois de defini-la como nula, seria uma otimização segura remover a linha que define o valor como nulo (uma instrução a menos para executar). Portanto, você pode não estar realmente obtendo nenhuma melhora.

Resumindo, sim, pode ajudar, mas não será determinístico .

sem nome
fonte
Um bom ponto sobre a otimização segura que remove a linha que define o ref como nulo.
asgs de
8

Pelo menos em java, não é programação vodu. Quando você cria um objeto em java usando algo como

Foo bar = new Foo();

você faz duas coisas: primeiro, você cria uma referência a um objeto e, segundo, cria o próprio objeto Foo. Enquanto essa referência ou outra existir, o objeto específico não pode ser gc'd. no entanto, quando você atribui nulo a essa referência ...

bar = null ;

e assumindo que nada mais tenha uma referência ao objeto, ele é liberado e disponibilizado para gc na próxima vez que o coletor de lixo passar.

Charlie Martin
fonte
12
Mas sair do escopo fará a mesma coisa sem a linha extra de código. Se for local para um método, não há necessidade. Depois de sair do método, o objeto será elegível para GC.
duffymo
2
Boa. agora, para um teste rápido, construa um exemplo de código para o qual NÃO é suficiente. Dica: eles existem.
Charlie Martin
7

Depende.

Em geral, quanto mais curto você manter as referências aos seus objetos, mais rápido eles serão coletados.

Se seu método leva, digamos, 2 segundos para ser executado e você não precisa mais de um objeto após um segundo de execução do método, faz sentido limpar todas as referências a ele. Se o GC vir isso depois de um segundo, seu objeto ainda está referenciado, da próxima vez ele pode verificar em um minuto ou assim.

De qualquer forma, definir todas as referências como nulas por padrão é para mim uma otimização prematura e ninguém deve fazer isso, a menos que em casos raros específicos em que diminua o consumo de memória de forma mensurável.

lubos hasko
fonte
6

Definir explicitamente uma referência como nulo em vez de apenas deixar a variável sair do escopo não ajuda o coletor de lixo, a menos que o objeto mantido seja muito grande, onde defini-lo como nulo assim que terminar é uma boa ideia.

Geralmente, definir referências como null significa para o LEITOR do código com o qual este objeto está completamente concluído e não deve se preocupar mais com ele.

Um efeito semelhante pode ser obtido introduzindo um escopo mais estreito, colocando um conjunto extra de suportes

{
  int l;
  {  // <- here
    String bigThing = ....;
    l = bigThing.length();
  }  // <- and here
}

isso permite que o bigThing seja coletado como lixo logo após deixar as chaves aninhadas.

Thorbjørn Ravn Andersen
fonte
Boa alternativa. Isso evita definir explicitamente a variável como nula e o aviso do lint que alguns IDEs exibem sobre que a atribuição nula não está sendo usada.
jk7
5
public class JavaMemory {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            byte[] data = new byte[dataSize];
            //data = null;
        }

        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {

        JavaMemory jmp = new JavaMemory();
        jmp.f();

    }

}

Acima dos lances do programa OutOfMemoryError. Se você descomentar data = null;, o OutOfMemoryErroré resolvido. É sempre uma boa prática definir a variável não utilizada como nula

Omiel
fonte
Isto é, imo, uma incursão sólida no mundo dos argumentos do espantalho. Só porque você PODE criar uma situação em que definir a variável como nula resolverá o problema nesse caso específico (e não estou convencido de que você tenha feito isso), não significa que defini-la como nula seja uma boa ideia em todos os casos. Adicionar o código para definir todas as variáveis ​​que você não está mais usando como nulas, mesmo quando elas sairiam do escopo logo em seguida, apenas aumenta o volume do código e torna o código menos sustentável.
RHSeeger
Por que o lixo do array byte [] não é coletado no momento em que os dados variáveis ​​estão fora do escopo?
Gravação de
2
@Grav: sair do escopo não garante que o coletor de lixo coletará os objetos locais. No entanto, defini-lo como nulo evita a exceção OutOfMemory, porque você tem a garantia de que o GC será executado antes de lançar uma exceção OutOfMemory e, se o objeto foi definido como nulo, ele poderá ser coletado.
Stefanos T.
Consulte também stackoverflow.com/questions/31260594/…
Raedwald
4

Eu estava trabalhando em um aplicativo de videoconferência uma vez e notei uma enorme diferença enorme no desempenho quando dediquei um tempo para referências nulas assim que não precisei mais do objeto. Isso foi em 2003-2004 e só posso imaginar que o GC ficou ainda mais inteligente desde então. No meu caso, eu tinha centenas de objetos entrando e saindo do escopo a cada segundo, então percebi o GC quando ele entrava em ação periodicamente. No entanto, depois que fiz questão de objetos nulos, o GC parou de pausar meu aplicativo.

Então, depende do que você está fazendo ...

Perada
fonte
2

Sim.

De "The Pragmatic Programmer" p.292:

Ao definir uma referência para NULL, você reduz o número de ponteiros para o objeto em um ... (o que permitirá que o coletor de lixo o remova)


fonte
Eu acho que isso é aplicável apenas quando algo de contagem de referência. está em uso ou também?
Nrj
3
Este conselho é obsoleto para qualquer coletor de lixo moderno; e a contagem de referência GC tem problemas significativos, como referências circulares.
Lawrence Dol
Também seria verdade em uma marcação e varredura GC; provavelmente em todos os outros. O fato de você ter uma referência a menos não significa que sua referência seja contada. A resposta por tweakt explica isso. É melhor reduzir o escopo do que definir como NULL.
1

Presumo que o OP está se referindo a coisas como estas:

private void Blah()
{
    MyObj a;
    MyObj b;

    try {
        a = new MyObj();
        b = new MyObj;

        // do real work
    } finally {
        a = null;
        b = null;
    }
}

Nesse caso, a VM não os marcaria para GC assim que saíssem do escopo?

Ou, de outra perspectiva, definir explicitamente os itens como nulos faria com que eles obtivessem GC antes que fariam se simplesmente saíssem do escopo? Em caso afirmativo, a VM pode gastar tempo GC'ing o objeto quando a memória não é necessária de qualquer maneira, o que poderia causar pior desempenho no uso da CPU porque seria GC'ing mais cedo.

CodingWithSpike
fonte
O tempo em que o GC é executado não é determinístico. Não acredito que definir um objeto nullinfluencie o comportamento do GC.
harto
0

" Depende "

Eu não sei sobre Java, mas em .net (C #, VB.net ...) geralmente não é necessário atribuir um nulo quando você não precisa mais de um objeto.

No entanto, observe que "geralmente não é necessário".

Ao analisar seu código, o compilador .net faz uma boa avaliação do tempo de vida da variável ... para dizer com precisão quando o objeto não está mais sendo usado. Então, se você escrever obj = null, pode parecer que o obj ainda está sendo usado ... neste caso, é contraproducente atribuir um nulo.

Existem alguns casos em que pode realmente ajudar atribuir um nulo. Um exemplo é que você tem um código enorme que é executado por um longo tempo ou um método que está sendo executado em uma thread diferente ou algum loop. Nesses casos, pode ser útil atribuir nulo para que seja fácil para o GC saber que não está mais sendo usado.

Não existe uma regra rígida e rápida para isso. Indo pelo acima, coloque atribuições nulas em seu código e execute um criador de perfil para ver se isso ajuda de alguma forma. Muito provavelmente você não verá um benefício.

Se for um código .net que você está tentando otimizar, então minha experiência tem sido que cuidar bem dos métodos Dispose e Finalize é na verdade mais benéfico do que se preocupar com nulos.

Algumas referências sobre o tema:

http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx

http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx

Sesh
fonte
0

Mesmo se anular a referência fosse um pouco mais eficiente, valeria a pena ter que salpicar seu código com essas nulificações horríveis? Eles seriam apenas desordem e obscureceriam o código de intenção que os contém.

É uma base de código rara que não tem melhor candidato para otimização do que tentar superar o coletor de lixo (mais raros ainda são os desenvolvedores que conseguem superá-lo). Provavelmente, seus esforços serão mais bem empregados em outro lugar, abandonando aquele analisador Xml crufty ou encontrando alguma oportunidade de cache de computação. Essas otimizações serão mais fáceis de quantificar e não exigem que você suja sua base de código com ruído.

sgargan
fonte
0

Na execução futura de seu programa, os valores de alguns membros de dados serão usados ​​para computar uma saída visível externa ao programa. Outros podem ou não ser usados, dependendo das entradas futuras (e impossíveis de prever) para o programa. Outros membros de dados podem ter a garantia de não serem usados. Todos os recursos, incluindo memória, alocados para os dados não utilizados são desperdiçados. O trabalho do coletor de lixo (GC) é eliminar essa memória desperdiçada. Seria desastroso para o GC eliminar algo que era necessário, então o algoritmo usado pode ser conservador, retendo mais do que o mínimo estrito. Ele pode usar otimizações heurísticas para melhorar sua velocidade, ao custo de reter alguns itens que não são realmente necessários. Existem muitos algoritmos potenciais que o GC pode usar. Portanto, é possível que alterações feitas em seu programa, e que não afetam a correção de seu programa, podem, no entanto, afetar a operação do GC, tornando-o mais rápido para fazer o mesmo trabalho ou para identificar itens não utilizados mais cedo. Portanto, esse tipo de mudança, definindo uma referência de objeto inusitadonull, em teoria nem sempre é vodu.

Isso é vodu? Segundo informações, há partes do código da biblioteca Java que fazem isso. Os escritores desse código são muito melhores do que os programadores médios e conhecem ou cooperam com programadores que conhecem detalhes das implementações do coletor de lixo. Assim que sugere que é , por vezes, um benefício.

Raedwald
fonte
0

Como você disse, existem otimizações, ou seja, a JVM sabe o local quando a variável foi usada pela última vez e o objeto referenciado por ela pode ser GCed logo após este último ponto (ainda em execução no escopo atual). Portanto, anular as referências na maioria dos casos não ajuda o GC.

Mas pode ser útil para evitar o problema do "nepotismo" (ou "lixo flutuante") ( leia mais aqui ou assista ao vídeo ). O problema existe porque a pilha é dividida nas gerações Velha e Jovem e existem diferentes mecanismos de GC aplicados: Minor GC (que é rápido e acontece frequentemente para limpar o jovem gen) e Major Gc (que causa uma pausa mais longa para limpar o Old gen). O "nepotismo" não permite que o lixo da geração jovem seja coletado se for referenciado por lixo já adquirido por uma geração antiga.

Isso é 'patológico' porque QUALQUER nó promovido resultará na promoção de TODOS os nós seguintes até que um GC resolva o problema.

Para evitar o nepotismo, é uma boa ideia anular as referências de um objeto que deve ser removido. Você pode ver essa técnica aplicada nas classes JDK: LinkedList e LinkedHashMap

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // ...
}
Kirill
fonte