Os dois principais argumentos contra a substituição Object.finalize()
são:
Você não decide quando é chamado.
Pode não ser chamado.
Se eu entendi isso corretamente, não acho que essas sejam boas razões para odiar Object.finalize()
tanto.
Depende da implementação da VM e do GC determinar quando é o momento certo para desalocar um objeto, não o desenvolvedor. Por que é importante decidir quando
Object.finalize()
é chamado?Normalmente, e me corrija se estiver errado, o único momento em
Object.finalize()
que não seria chamado é quando o aplicativo era encerrado antes que o GC tivesse a chance de ser executado. No entanto, o objeto foi desalocado de qualquer maneira quando o processo do aplicativo foi encerrado com ele. PortantoObject.finalize()
, não foi chamado porque não precisava ser chamado. Por que o desenvolvedor se importaria?
Toda vez que estou usando objetos que tenho que fechar manualmente (como identificadores e conexões de arquivos), fico muito frustrado. Eu tenho que verificar constantemente se um objeto possui uma implementação close()
e tenho certeza de que perdi algumas chamadas para ele em alguns pontos no passado. Por que não é apenas mais simples e seguro deixar a VM e o GC descartarem esses objetos colocando a close()
implementação Object.finalize()
?
fonte
finalize()
é um pouco complicada. Se você implementá-lo, verifique se ele é seguro para threads em relação a todos os outros métodos no mesmo objeto.Respostas:
Na minha experiência, há uma e apenas uma razão para substituir
Object.finalize()
, mas é uma boa razão :A análise estática pode capturar apenas omissões em cenários de uso trivial, e os avisos do compilador mencionados em outra resposta têm uma visão tão simplista das coisas que você realmente precisa desativá-las para fazer qualquer coisa não trivial. (Tenho muito mais avisos ativados do que qualquer outro programador que eu conheço ou já ouvi falar, mas não tenho avisos estúpidos ativados.)
A finalização pode parecer um bom mecanismo para garantir que os recursos não fiquem indisponíveis, mas a maioria das pessoas vê isso de uma maneira completamente errada: eles pensam nisso como um mecanismo alternativo de fallback, uma "segunda chance" de salvaguarda que salvará automaticamente o dia, descartando os recursos que eles esqueceram. Isso está completamente errado . Deve haver apenas uma maneira de fazer qualquer coisa: você sempre fecha tudo ou a finalização sempre fecha tudo. Mas como a finalização não é confiável, a finalização não pode ser isso.
Portanto, existe esse esquema que chamo de Disposição Obrigatória , e estipula que o programador é responsável por sempre fechar explicitamente tudo o que implementa
Closeable
ouAutoCloseable
. (A instrução try-with-resources ainda conta como fechamento explícito.) É claro que o programador pode esquecer, então é aí que a finalização entra em jogo, mas não como uma fada mágica que, magicamente, fará as coisas corretamente no final: se a finalização descobrir queclose()
não foi invocado, nãotente invocá-lo, precisamente porque (com certeza matemática) haverá hordas de programadores do n00b que confiarão nele para fazer o trabalho que eles eram preguiçosos ou distraídos demais para fazer. Portanto, com o descarte obrigatório, quando a finalização descobre queclose()
não foi invocada, ela registra uma mensagem de erro em vermelho brilhante, dizendo ao programador com letras maiúsculas grandes e grandes para consertar suas coisas.Como um benefício adicional, dizem os boatos de que "a JVM ignorará um método trivial finalize () (por exemplo, um que apenas retorna sem fazer nada, como o definido na classe Object)"; portanto, com o descarte obrigatório, você pode evitar toda finalização sobrecarga em todo o sistema ( consulte a resposta da alip para obter informações sobre quão terrível é essa sobrecarga) codificando seu
finalize()
método da seguinte maneira:A idéia por trás disso é que
Global.DEBUG
é umastatic final
variável cujo valor é conhecido no momento da compilação; portanto, se forfalse
, o compilador não emitirá nenhum código para toda aif
instrução, o que tornará este um finalizador trivial (vazio), que por sua vez significa que sua turma será tratada como se não tivesse um finalizador. (Em C #, isso seria feito com um bom#if DEBUG
bloco, mas o que podemos fazer, isso é java, onde pagamos aparente simplicidade no código com sobrecarga adicional no cérebro.)Mais sobre o descarte obrigatório, com discussões adicionais sobre o descarte de recursos no dot Net, aqui: michael.gr: descarte obrigatório versus a abominação "Dispose-descarte"
fonte
close()
, por precaução. Acredito que, se meus testes não são confiáveis, é melhor não liberar o sistema para produção.if( Global.DEBUG && ...
construção funcione, de modo que a JVM ignore ofinalize()
método como trivial,Global.DEBUG
deve ser configurada no tempo de compilação (em oposição a injetado etc.), para que o que se segue seja código morto. A chamada parasuper.finalize()
fora do bloco if também é suficiente para a JVM tratá-lo como não trivial (independentemente do valor deGlobal.DEBUG
, pelo menos no HotSpot 1.8), mesmo que a superclasse#finalize()
seja trivial!java.io
. Se esse tipo de segurança de thread não estava na lista de desejos, isso aumenta a sobrecarga induzida porfinalize()
...Como identificadores e conexões de arquivos (ou seja, descritores de arquivos nos sistemas Linux e POSIX) são um recurso bastante escasso (você pode estar limitado a 256 deles em alguns sistemas ou a 16384 em outros; veja setrlimit (2) ). Não há garantia de que o GC seja chamado com bastante frequência (ou no momento certo) para evitar o esgotamento de tais recursos limitados. E se o GC não for chamado o suficiente (ou a finalização não for executada no momento certo), você atingirá esse limite (possivelmente baixo).
A finalização é uma coisa de "melhor esforço" na JVM. Pode não ser chamado ou tarde demais ... Em particular, se você tem muita RAM ou se o seu programa não aloca muitos objetos (ou se a maioria deles morre antes de ser encaminhada para alguém com idade suficiente) geração por um GC geracional de cópia), o GC pode ser chamado muito raramente, e as finalizações podem não ser executadas com muita frequência (ou até mesmo não serem executadas).
Portanto, descreva os
close
arquivos explicitamente, se possível. Se você tem medo de vazá-los, use a finalização como uma medida extra, não como a principal.fonte
Veja o problema desta maneira: você só deve escrever um código que seja (a) correto (caso contrário, seu programa está completamente errado) e (b) necessário (caso contrário, seu código é muito grande, o que significa mais RAM necessária, mais ciclos gastos em coisas inúteis, mais esforço para entendê-lo, mais tempo gasto em mantê-lo etc. etc.)
Agora considere o que você deseja fazer em um finalizador. Ou é necessário. Nesse caso, você não pode colocá-lo em um finalizador, porque você não sabe se ele será chamado. Isso não é bom o suficiente. Ou não é necessário - então você não deveria escrevê-lo em primeiro lugar! De qualquer forma, colocá-lo no finalizador é a escolha errada.
(Observe que os exemplos que você nomeia, como fechar fluxos de arquivos, parecem que não são realmente necessários, mas são. É apenas que, até atingir o limite de identificadores de arquivos abertos em seu sistema, você não notará que seus O código está incorreto. Mas esse limite é um recurso do sistema operacional e, portanto, é ainda mais imprevisível do que a política da JVM para finalizadores; portanto, é realmente importante que você não desperdice identificadores de arquivo.)
fonte
finalize()
-lo para fechar caso um ciclo gc realmente precise liberar RAM. Caso contrário, eu manteria o objeto na RAM, em vez de precisar regenerá-lo e fechá-lo toda vez que precisar usá-lo. Claro, os recursos que ele abre não serão liberados até que o objeto seja analisado em GC, sempre que possível, mas talvez não seja necessário garantir que meus recursos sejam liberados em um determinado momento.Uma das maiores razões para não confiar nos finalizadores é que a maioria dos recursos que se pode tentar limpar no finalizador são muito limitados. O coletor de lixo é executado apenas de vez em quando, pois percorre referências para determinar se algo pode ser liberado ou não é caro. Isso significa que pode demorar um pouco até que seus objetos sejam destruídos. Se você tiver muitos objetos abrindo conexões de banco de dados de curta duração, por exemplo, deixar os finalizadores para limpar essas conexões pode esgotar seu conjunto de conexões enquanto aguarda o coletor de lixo finalmente executar e liberar as conexões concluídas. Então, devido à espera, você acaba com um grande estoque de solicitações enfileiradas, que esgotam rapidamente o conjunto de conexões novamente. Isto'
Além disso, o uso do try-with-resources facilita o fechamento de objetos 'fechados' na conclusão. Se você não estiver familiarizado com essa construção, sugiro que verifique: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
fonte
Além do motivo pelo qual deixar para o finalizador liberar recursos geralmente é uma má idéia, os objetos finalizáveis vêm com sobrecarga de desempenho.
Da teoria e prática Java: Coleta de lixo e desempenho (Brian Goetz), os finalizadores não são seus amigos :
fonte
finalize()
.Meu (menos) motivo favorito para evitar
Object.finalize
não é que os objetos possam ser finalizados depois que você espera, mas eles podem ser finalizados antes que você possa esperar. O problema não é que um objeto que ainda esteja no escopo possa ser finalizado antes que o escopo seja encerrado, se Java decidir que não é mais acessível.Veja esta pergunta para mais detalhes. Ainda mais divertido é que essa decisão só pode ser tomada após a otimização do hot-spot, tornando difícil a depuração.
fonte
HasFinalize.stream
ele próprio deve ser um objeto finalizável separadamente. Ou seja, a finalização deHasFinalize
não deve finalizar ou tentar limparstream
. Ou, se deveria, deveria ficarstream
inacessível.No Eclipse, recebo um aviso sempre que esqueço de fechar algo que implementa
Closeable
/AutoCloseable
. Não tenho certeza se isso é algo do Eclipse ou se faz parte do compilador oficial, mas você pode usar ferramentas de análise estática semelhantes para ajudá-lo lá. O FindBugs, por exemplo, provavelmente pode ajudá-lo a verificar se você esqueceu de fechar um recurso.fonte
AutoCloseable
. Isso facilita o gerenciamento de recursos com tentativa com recursos. Isso anula vários dos argumentos da pergunta.Para sua primeira pergunta:
Bem, a JVM determinará quando é um bom ponto recuperar o armazenamento que foi alocado para um objeto. Esse não é necessariamente o momento em que o recurso de limpeza, no qual você deseja executar
finalize()
, deve ocorrer. Isso é ilustrado na pergunta “finalize () chamado objeto altamente alcançável em Java 8” no SO. Lá, umclose()
método foi chamado por umfinalize()
método, enquanto uma tentativa de ler do fluxo pelo mesmo objeto ainda está pendente. Portanto, além da possibilidade bem conhecida,finalize()
chamada tarde demais, existe a possibilidade de ser chamada cedo demais.A premissa de sua segunda pergunta:
está simplesmente errado. Não há nenhum requisito para uma JVM suportar a finalização. Bem, não está completamente errado, pois você ainda pode interpretá-lo como "o aplicativo foi encerrado antes da finalização", supondo que o seu aplicativo seja encerrado.
Mas observe a pequena diferença entre "GC" da sua declaração original e o termo "finalização". A coleta de lixo é distinta da finalização. Uma vez que o gerenciamento de memória detecte que um objeto está inacessível, ele pode simplesmente recuperar seu espaço, caso contrário, não possui um
finalize()
método especial ou a finalização é simplesmente não suportada ou pode enfileirar o objeto para finalização. Portanto, a conclusão de um ciclo de coleta de lixo não implica que os finalizadores sejam executados. Isso pode acontecer posteriormente, quando a fila é processada, ou nunca.Esse ponto também é o motivo pelo qual, mesmo em JVMs com suporte à finalização, confiar nele para a limpeza de recursos é perigoso. A coleta de lixo faz parte do gerenciamento de memória e, portanto, é acionada pelas necessidades de memória. É possível que a coleta de lixo nunca seja executada porque há memória suficiente durante todo o tempo de execução (bem, isso ainda se encaixa na descrição “o aplicativo foi encerrado antes que o GC tivesse a chance de executar”), de alguma forma). Também é possível que o GC seja executado, mas, posteriormente, há memória recuperada suficiente, para que a fila do finalizador não seja processada.
Em outras palavras, os recursos nativos gerenciados dessa maneira ainda são estranhos ao gerenciamento de memória. Embora seja garantido que um
OutOfMemoryError
é lançado somente após tentativas suficientes para liberar memória, isso não se aplica aos recursos e finalização nativos. É possível que a abertura de um arquivo falhe devido a recursos insuficientes, enquanto a fila de finalização estiver cheia de objetos que poderiam liberar esses recursos, se o finalizador alguma vez…fonte