O uso de final para variáveis ​​em Java melhora a coleta de lixo?


86

Hoje, meus colegas e eu temos uma discussão sobre o uso da finalpalavra - chave em Java para melhorar a coleta de lixo.

Por exemplo, se você escrever um método como:

public Double doCalc(final Double value)
{
   final Double maxWeight = 1000.0;
   final Double totalWeight = maxWeight * value;
   return totalWeight;  
}

Declarar as variáveis ​​no método finalajudaria a coleta de lixo a limpar a memória das variáveis ​​não utilizadas no método após a saída do método.

Isso é verdade?


duas coisas aqui, na verdade. 1) quando você grava em um campo local do método e uma instância. Quando você escreve para uma instância, pode haver um benefício.
Eugene

Respostas:


86

Aqui está um exemplo ligeiramente diferente, um com campos de tipo de referência finais em vez de variáveis ​​locais de tipo de valor final:

public class MyClass {

   public final MyOtherObject obj;

}

Cada vez que você cria uma instância de MyClass, você estará criando uma referência de saída para uma instância MyOtherObject, e o GC terá que seguir esse link para procurar por objetos ativos.

A JVM usa um algoritmo GC de varredura de marcação, que deve examinar todas as referências ativas nas localizações "raiz" do GC (como todos os objetos na pilha de chamadas atual). Cada objeto vivo é "marcado" como estando vivo e qualquer objeto referido por um objeto vivo também é marcado como vivo.

Após a conclusão da fase de marcação, o GC varre o heap, liberando memória para todos os objetos não marcados (e compactando a memória para os objetos vivos restantes).

Além disso, é importante reconhecer que a memória heap Java é particionada em uma "geração jovem" e uma "geração antiga". Todos os objetos são inicialmente alocados na geração jovem (às vezes chamada de "berçário"). Como a maioria dos objetos tem vida curta, o GC é mais agressivo em liberar o lixo recente da geração jovem. Se um objeto sobrevive a um ciclo de coleção da geração jovem, ele é movido para a geração anterior (às vezes chamada de "geração permanente"), que é processada com menos frequência.

Então, de repente, vou dizer "não, o modificador 'final' não ajuda o GC a reduzir sua carga de trabalho".

Em minha opinião, a melhor estratégia para otimizar o gerenciamento de memória em Java é eliminar referências espúrias o mais rápido possível. Você poderia fazer isso atribuindo "null" a uma referência de objeto assim que terminar de usá-lo.

Ou, melhor ainda, minimize o tamanho de cada escopo de declaração. Por exemplo, se você declarar um objeto no início de um método de 1000 linhas, e se o objeto permanecer vivo até o fechamento do escopo do método (a última chave de fechamento), então o objeto pode permanecer vivo por muito mais tempo do que realmente necessário.

Se você usar métodos pequenos, com apenas uma dúzia ou mais de linhas de código, então os objetos declarados dentro desse método sairão do escopo mais rapidamente e o GC será capaz de fazer a maior parte de seu trabalho dentro do muito mais eficiente geração jovem. Você não quer que os objetos sejam movidos para a geração anterior, a menos que seja absolutamente necessário.


Alimento para o pensamento. Sempre pensei que o código embutido era mais rápido, mas se o jvm ficar sem memória, ele também ficará lento. hmmmmm ...
WolfmanDragon

1
Estou apenas supondo aqui ... mas presumo que o compilador JIT pode incorporar valores primitivos finais (não objetos), para aumentos modestos de desempenho. O inlining de código, por outro lado, é capaz de produzir otimizações significativas, mas não tem nada a ver com as variáveis ​​finais.
benjismith

2
Não seria possível atribuir null a um objeto final já criado, então talvez final em vez de ajuda, poderia tornar as coisas mais difíceis
Hernán Eche

1
Pode-se usar {} para limitar o escopo em um método grande também, em vez de dividi-lo em vários métodos privados que podem não ser relevantes para outros métodos de classe.
mmm

Você também pode usar variáveis ​​locais em vez de campos quando for possível, a fim de aprimorar a coleta de lixo da memória e reduzir os laços referenciais.
sivi de

37

Declarar uma variável local finalnão afetará a coleta de lixo, significa apenas que você não pode modificar a variável. Seu exemplo acima não deve ser compilado porque você está modificando a variável totalWeightque foi marcada final. Por outro lado, declarar uma vontade primitiva (em doublevez de Double) finalpermite que a variável seja embutida no código de chamada, de modo que isso poderia causar alguma melhoria de memória e desempenho. Isso é usado quando você tem vários public static final Stringsem uma classe.

Em geral, o compilador e o tempo de execução otimizam onde podem. É melhor escrever o código apropriadamente e não tentar ser muito complicado. Use finalquando não quiser que a variável seja modificada. Suponha que qualquer otimização fácil será executada pelo compilador e, se você estiver preocupado com o desempenho ou o uso de memória, use um criador de perfil para determinar o problema real.


26

Não, enfaticamente não é verdade.

Lembre-se de que finalisso não significa constante, apenas significa que você não pode alterar a referência.

final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.

Pode haver alguma pequena otimização baseada no conhecimento de que a JVM nunca terá que modificar a referência (como não ter que verificar se ela mudou), mas seria tão pequena a ponto de não se preocupar.

Final devem ser considerados meta-dados úteis para o desenvolvedor e não como uma otimização do compilador.


17

Alguns pontos a serem esclarecidos:

  • Anular a referência não deve ajudar o GC. Se sim, isso indicaria que suas variáveis ​​estão com escopo além do limite. Uma exceção é o caso do nepotismo de objeto.

  • Ainda não há alocação na pilha em Java.

  • Declarar uma variável final significa que você não pode (em condições normais) atribuir um novo valor a essa variável. Como o final não diz nada sobre o escopo, ele não diz nada sobre seu efeito no GC.


Há alocação on-stack (de primitivos e referências a objetos no heap) em java: stackoverflow.com/a/8061692/32453 Java requer que objetos no mesmo fechamento que uma classe anônima / lambda também sejam finais, mas transforma isso é apenas para reduzir a "memória necessária / quadro" / reduzir a confusão, então não está relacionado à sua coleta ...
rogerdpack

11

Bem, eu não sei sobre o uso do modificador "final" neste caso, ou seu efeito no GC.

Mas posso te dizer uma coisa: o uso de valores Boxed em vez de primitivos (por exemplo, Double em vez de double) alocará esses objetos no heap em vez de na pilha, e produzirá lixo desnecessário que o GC terá que limpar.

Eu só uso primitivos em caixa quando exigido por uma API existente ou quando preciso de primitivos anuláveis.


1
Você está certo. Eu só precisava de um exemplo rápido para explicar minha pergunta.
Goran Martinic de

5

As variáveis ​​finais não podem ser alteradas após a atribuição inicial (reforçada pelo compilador).

Isso não altera o comportamento da coleta de lixo como tal. A única coisa é que essas variáveis ​​não podem ser anuladas quando não estão mais sendo usadas (o que pode ajudar na coleta de lixo em situações de memória apertada).

Você deve saber que o final permite que o compilador faça suposições sobre o que otimizar. Código embutido e não incluindo código conhecido por não ser alcançável.

final boolean debug = false;

......

if (debug) {
  System.out.println("DEBUG INFO!");
}

O println não será incluído no código de byte.


@Eugene Depende do seu gerenciador de segurança e se o compilador embutiu a variável ou não.
Thorbjørn Ravn Andersen

certo, eu estava apenas sendo pedante; nada mais; também forneceu uma resposta também
Eugene

4

Existe um caso de esquina não tão conhecido com os coletores de lixo de gerações. (Para uma breve descrição, leia a resposta de benjismith para uma visão mais profunda, leia os artigos no final).

A ideia em OGs geracionais é que na maioria das vezes apenas as gerações mais jovens precisam ser consideradas. O local raiz é verificado em busca de referências e, em seguida, os objetos da geração mais jovem são verificados. Durante essas varreduras mais frequentes, nenhum objeto da geração anterior é verificado.

Agora, o problema vem do fato de que um objeto não pode ter referências a objetos mais jovens. Quando um objeto de longa duração (geração anterior) obtém uma referência para um novo objeto, essa referência deve ser explicitamente rastreada pelo coletor de lixo (consulte o artigo da IBM sobre o coletor JVM de ponto de acesso ), afetando de fato o desempenho do GC.

A razão pela qual um objeto antigo não pode se referir a um mais jovem é que, como o objeto antigo não é verificado em coleções secundárias, se a única referência ao objeto for mantida no objeto antigo, ele não será marcado e seria incorretamente desalocado durante o estágio de varredura.

Obviamente, como apontado por muitos, a palavra-chave final não afeta realmente o coletor de lixo, mas garante que a referência nunca será alterada para um objeto mais jovem se esse objeto sobreviver às coleções secundárias e chegar ao heap mais antigo.

Artigos:

IBM na coleta de lixo: história , no ponto de acesso JVM e desempenho . Eles podem não ser mais totalmente válidos, visto que remontam a 2003/04, mas fornecem uma visão fácil de ler sobre GCs.

Sun on Tuning coleta de lixo


3

GC age em referências inacessíveis. Isso não tem nada a ver com "final", que é apenas uma afirmação de atribuição única. É possível que alguns VM's GC possam fazer uso de "final"? Não vejo como ou por quê.


3

finalem variáveis ​​e parâmetros locais não faz diferença para os arquivos de classe produzidos, portanto, não pode afetar o desempenho do tempo de execução. Se uma classe não tem subclasses, o HotSpot trata essa classe como se fosse final de qualquer maneira (ele pode desfazer mais tarde se uma classe que quebra essa suposição for carregada). Eu acredito que finalmétodos é muito parecido com aulas. finalno campo estático pode permitir que a variável seja interpretada como uma "constante de tempo de compilação" e a otimização seja feita por javac nessa base. finalnos campos permite à JVM alguma liberdade para ignorar as relações acontece antes .


2

Parece haver muitas respostas que são conjecturas errantes. A verdade é que não há modificador final para variáveis ​​locais no nível de bytecode. A máquina virtual nunca saberá se suas variáveis ​​locais foram definidas como finais ou não.

A resposta à sua pergunta é um enfático não.


Isso pode ser verdade, mas o compilador ainda pode usar as informações finais durante a análise do fluxo de dados.

@WernerVanBelle o compilador já sabe que uma variável é definida apenas uma vez. Ele tem que fazer uma análise de fluxo de dados já para saber que uma variável pode ser nula, não é inicializada antes do uso, etc. Portanto, os finais locais não oferecem nenhuma informação nova ao compilador.
Matt Quigley de

Não é verdade. A análise de fluxo de dados pode deduzir muitas coisas, mas é possível ter um programa completo em um bloco que definirá ou não uma variável local. O compilador não pode saber com antecedência se a variável será escrita e será constante. Portanto, o compilador, sem a palavra-chave final, não pode garantir que uma variável seja final ou não.

@WernerVanBelle Estou genuinamente intrigado, você pode dar um exemplo? Não vejo como pode haver uma atribuição final a uma variável não final que o compilador não conhece. O compilador sabe que se você tiver uma variável não inicializada, ele não permitirá que você a use. Se você tentar atribuir uma variável final dentro de um loop, o compilador não permitirá. Qual é um exemplo em que uma variável que PODE ser declarada como final, mas NÃO é, e o compilador não pode garantir que ela seja final? Suspeito que qualquer exemplo seria uma variável que não poderia ser declarada como final em primeiro lugar.
Matt Quigley

Depois de reler seu comentário, vejo que seu exemplo é uma variável inicializada dentro de um bloco condicional. Essas variáveis ​​não podem ser finais em primeiro lugar, a menos que todos os caminhos de condição inicializem a variável uma vez. Então, o compilador sabe sobre essas declarações - é assim que ele permite que você compile uma variável final inicializada em dois lugares diferentes (pense final int x; if (cond) x=1; else x=2;). O compilador, sem a palavra-chave final, pode garantir que uma variável seja final ou não.
Matt Quigley

1

Todos os métodos e variáveis ​​podem ser sobrescritos por padrão nas subclasses. Se quisermos salvar as subclasses da sobrescrita dos membros da superclasse, podemos declará-los como finais usando a palavra-chave final. Por exemplo, final int a=10; final void display(){......} tornar um método final garante que a funcionalidade definida na superclasse nunca será alterada de qualquer maneira. Da mesma forma, o valor de uma variável final nunca pode ser alterado. Variáveis ​​finais se comportam como variáveis ​​de classe.


1

Estritamente falando sobre campos de instância , final pode melhorar um pouco o desempenho se um determinado GC quiser explorar isso. Quando um concorrenteGC ocorre (isso significa que seu aplicativo ainda está em execução, enquanto o GC está em andamento ), veja isso para uma explicação mais ampla , os GCs têm que empregar certas barreiras quando as gravações e / ou leituras são feitas. O link que eu forneci explica muito bem isso, mas para torná-lo bem curto: quando a GCfaz algum trabalho simultâneo, todos leem e gravam no heap (enquanto o GC está em andamento), são "interceptados" e aplicados posteriormente; para que a fase de GC simultânea possa terminar seu trabalho.

Por finalexemplo, campos, uma vez que não podem ser modificados (exceto reflexão), essas barreiras podem ser omitidas. E isso não é apenas teoria pura.

Shenandoah GCos tem em prática (embora não por muito tempo ), e você pode fazer, por exemplo:

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

E haverá otimizações no algoritmo de GC que o tornarão um pouco mais rápido. Isso porque não haverá interceptação de barreiras final, já que ninguém deve modificá-las, nunca. Nem mesmo via reflexão ou JNI.


0

A única coisa que posso pensar é que o compilador pode otimizar as variáveis ​​finais e embuti-las como constantes no código, assim você acaba sem memória alocada.


0

absolutamente, desde que torne a vida do objeto mais curta, o que gera grande benefício de gerenciamento de memória, recentemente examinamos a funcionalidade de exportação com variáveis ​​de instância em um teste e outro teste com variável local de nível de método. durante o teste de carga, a JVM lança outofmemoryerror no primeiro teste e a JVM foi interrompida. mas no segundo teste, conseguiu obter o relatório devido a um melhor gerenciamento de memória.


0

O único momento em que prefiro declarar variáveis ​​locais como finais é quando:

  • Tenho que torná-los finais para que possam ser compartilhados com alguma classe anônima (por exemplo: criar o encadeamento daemon e deixá-lo acessar algum valor do método de inclusão)

  • Eu quero torná-los finais (por exemplo: algum valor que não deve / não deve ser substituído por engano)

Eles ajudam na coleta de lixo rápida?
AFAIK um objeto se torna um candidato à coleta de GC se não tiver referências fortes a ele e, nesse caso, também não há garantia de que eles serão imediatamente coletados como lixo. Em geral, diz-se que uma referência forte morre quando sai do escopo ou o usuário a reatribui explicitamente para a referência nula, portanto, declará-la final significa que a referência continuará a existir até que o método exista (a menos que seu escopo seja explicitamente reduzido para um bloco interno específico {}) porque você não pode reatribuir variáveis ​​finais (ou seja, não pode reatribuir para nulo). Portanto, acho que a coleta de lixo 'final' pode introduzir um possível atraso indesejado, portanto, deve-se ter um pouco de cuidado ao definir seu escopo, pois controla quando eles se tornarão candidatos para GC.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.