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.