Por que o método finalize está incluído no Java?

44

De acordo com este post , nunca devemos confiar no método finalize a ser chamado. Então, por que o Java o incluiu na linguagem de programação?

Parece uma decisão terrível incluir em qualquer linguagem de programação uma função que possa ser chamada.

patstuart
fonte

Respostas:

41

De acordo com o Effective Java de Joshua Bloch (Segunda Edição), há dois cenários em que finalize()é útil:

  1. Uma é agir como uma "rede de segurança", caso o proprietário de um objeto esqueça de chamar seu método explícito de terminação. Embora não haja garantia de que o finalizador seja chamado imediatamente, pode ser melhor liberar o recurso mais tarde do que nunca, naqueles casos (provavelmente raros) em que o cliente falha em chamar o método de encerramento explícito. Mas o finalizador deve registrar um aviso se descobrir que o recurso não foi finalizado

  2. Um segundo uso legítimo de finalizadores diz respeito a objetos com pares nativos. Um par nativo é um objeto nativo ao qual um objeto normal delega por métodos nativos. Como um par nativo não é um objeto normal, o coletor de lixo não o conhece e não pode recuperá-lo quando seu par Java é recuperado. Um finalizador é um veículo apropriado para executar esta tarefa, assumindo que o par nativo não possua recursos críticos. Se o par nativo reter recursos que devem ser finalizados imediatamente, a classe deverá ter um método de finalização explícito, conforme descrito acima. O método de encerramento deve fazer o que for necessário para liberar o recurso crítico.

Para uma leitura mais detalhada, consulte o Item 7, página 27.

bbalchev
fonte
4
Com o nº 2, parece que o par nativo é um recurso nativo que requer uma rede de segurança e deve ter um método de terminação e, portanto, não deve ser um ponto separado.
Mooing Duck
2
@MooingDuck: # 2 é um ponto separado porque, se o par nativo não possui recursos críticos (por exemplo, é puramente um objeto na memória), não há necessidade de expor um método de terminação explícito. A rede de segurança do nº 1 é explicitamente sobre ter um método de terminação.
Jhominal 26/06
55

Os finalizadores são importantes para o gerenciamento de recursos nativos. Por exemplo, seu objeto pode precisar alocar um WidgetHandle do sistema operacional usando uma API não Java. Se você não liberar esse WidgetHandle quando seu objeto estiver no GC, estará vazando WidgetHandles.

O importante é que os casos "finalizador nunca são chamados" sejam divididos de maneira simples:

  1. O programa é encerrado rapidamente
  2. O objeto "vive para sempre" durante a vida útil do programa
  3. O computador desliga / seu processo é interrompido pelo sistema operacional / etc

Nos três casos, você não possui um vazamento nativo (devido ao fato de seu programa não estar mais sendo executado) ou já possui um vazamento não nativo (se continuar alocando objetos gerenciados sem que eles sejam GC'd).

O aviso "não confie no finalizador que está sendo chamado" é realmente sobre não usar finalizadores para a lógica do programa. Por exemplo, você não deseja acompanhar quantos objetos existem em todas as instâncias do seu programa, incrementando um contador em um arquivo em algum lugar durante a construção e diminuindo-o em um finalizador - porque não há garantia de que seus objetos serão Ao finalizar, esse contador de arquivos provavelmente nunca voltará a 0. Esse é realmente um caso especial do princípio mais geral de que você não deve depender do término normal do programa (falhas de energia, etc.).

Para o gerenciamento de recursos nativos, os casos em que o finalizador não é executado correspondem aos casos em que você não se importa se não for executado.

Ryan Cavanaugh
fonte
Excelente resposta. Eu era como se fosse chamado ... ainda deveria ser incluído. Fiquei confuso por um tempo com a pergunta e a questão vinculada no StackOverflow.
InformedA
3
Não estava em questão, mas vale a pena discutir no contexto de "não confie no finalizador que está sendo chamado": O finalizador pode ser chamado, mas somente após um atraso arbitrariamente longo após o objeto ficar inacessível. Isso é importante para os "recursos nativos", que são muito mais escassos que a memória (a maioria deles, ao que parece).
26
"Os finalizadores são importantes para o gerenciamento de recursos nativos." - nooooooooo, ahem desculpe. Usar finalizadores para gerenciar recursos nativos é uma péssima idéia (é por isso que agora temos autocloseable e co). O GC se preocupa apenas com a memória, e não com qualquer outro recurso. Isso significa que você pode ficar sem recursos nativos, mas ainda tem memória suficiente para não acionar um GC - nós realmente não queremos isso. Além disso, os finalizadores tornam o GC mais caro, o que também não é uma boa ideia.
Voo
12
Eu concordo que você ainda deve ter uma estratégia de gestão de recursos nativa explícita diferente de finalizadores, mas se o seu objeto faz alocá-los e não libertá-los no finalizador, você está abrindo-se a um vazamento nativa no caso em que o objeto é GC sem a liberação explícita do recurso. Em outras palavras, os finalizadores são uma parte importante de uma estratégia de gerenciamento de recursos nativa, não uma solução para o problema em geral.
precisa
1
@Voo é provavelmente mais correto dizer o finalizador é o fallback no caso do contrato do objeto não é seguido ( close()nunca é chamado antes que se torne inacessível)
catraca aberração
5

O objetivo deste método é explicado na documentação da API da seguinte maneira:

é invocado se e quando a Java virtual machine determinou que não há mais nenhum meio pelo qual esse objeto possa ser acessado por qualquer encadeamento que ainda não tenha morrido, exceto como resultado de uma ação executada pela finalização de outro objeto ou classe que está pronta para ser finalizada ...

o objetivo usual de finalize... é executar ações de limpeza antes que o objeto seja descartado irrevogavelmente . Por exemplo, o método finalize para um objeto que representa uma conexão de entrada / saída pode executar transações de E / S explícitas para interromper a conexão antes que o objeto seja descartado permanentemente ...


Se você também estiver interessado em razões pelas quais os designers de linguagem optaram por "o objeto é irrevogavelmente descartado" ( coleta de lixo ) da maneira que está além do controle do programador de aplicativos ("nunca devemos confiar"), isso foi explicado em uma resposta a pergunta :

coleta automática de lixo ... elimina classes inteiras de erros de programação que atormentam os programadores de C e C ++. Você pode desenvolver código Java com confiança de que o sistema encontrará muitos erros rapidamente e que os principais problemas não permanecerão inativos até que o código de produção seja enviado.

A citação acima, por sua vez, foi tirada da documentação oficial sobre os objetivos do projeto Java , ou seja, pode ser considerada uma referência autorizada, explicando por que os designers da linguagem Java decidiram dessa maneira.

Para uma discussão mais detalhada e independente do idioma sobre essa preferência, consulte a seção 9.6 da OOSC . Gerenciamento automático de memória (na verdade, não apenas esta seção, mas todo o capítulo 9 vale muito a pena ler se você estiver interessado em coisas desse tipo). Esta seção é aberta com uma declaração inequívoca:

Um bom ambiente OO deve oferecer um mecanismo automático de gerenciamento de memória que detecte e recupere objetos inacessíveis, permitindo que os desenvolvedores de aplicativos se concentrem em seu trabalho - desenvolvimento de aplicativos.

A discussão anterior deve ser suficiente para mostrar o quanto é importante ter esse recurso disponível. Nas palavras de Michael Schweitzer e Lambert Strether:

Um programa orientado a objetos sem gerenciamento automático de memória é aproximadamente o mesmo que uma panela de pressão sem uma válvula de segurança: mais cedo ou mais tarde a coisa certamente explodirá!

mosquito
fonte
4

Os finalizadores existem porque se espera que eles sejam um meio eficaz de garantir que as coisas sejam limpas (embora na prática não sejam) e porque quando foram inventados, melhores meios de garantir a limpeza (como referências fantasmas e tentativa de -resources) ainda não existia. Em retrospectiva, o Java provavelmente seria melhor se o esforço despendido na implementação de sua instalação "finalize" tivesse sido gasto em outros meios de limpeza, mas isso não ficou claro durante o período em que o Java estava sendo desenvolvido inicialmente.

supercat
fonte
3

CAVEAT: Posso estar desatualizado, mas esse é o meu entendimento a alguns anos atrás:

Em geral, não há garantia de quando um finalizador é executado - ou mesmo que seja executado, embora algumas JVMs permitam solicitar um GC e uma finalização completos antes da saída do programa (o que, é claro, significa que o programa leva mais tempo sair e que não é o modo padrão de operação).

E sabia-se que alguns GCs atrasavam explicitamente ou evitavam objetos de GC que tinham finalizadores, na esperança de que isso produzisse melhor desempenho nos benchmarks.

Infelizmente, esses comportamentos conflitam com os motivos originais pelos quais os finalizadores foram recomendados e, em vez disso, incentivaram o uso de métodos de desligamento chamados explicitamente.

Se você tem um objeto que realmente precisa ser limpo antes de ser descartado e se não pode confiar nos usuários, um finalizador ainda pode ser considerado. Mas, em geral, existem boas razões para que você não as veja com tanta frequência no código Java moderno como em alguns dos primeiros exemplos.

keshlam
fonte
1

O Google Java Style Guide tem alguns sábios conselhos sobre o assunto:

É extremamente raro substituir Object.finalize.

Dica : não faça isso. Se você precisar, leia e entenda primeiro o Item eficaz do Java 7, "Evite finalizadores", com muito cuidado e, em seguida , não faça isso.

Daniel Pryden
fonte
5
Por que admiro a simplificação excessiva do problema, "não faça" não é uma resposta para "por que ele existe".
Pierre Arlaud
1
Acho que não escrevi bem na minha resposta, mas (IMO) a resposta para "por que existe?" é "não deveria". Para aqueles casos raros em que você realmente precisa de uma operação de finalização, provavelmente deseja construí-la com PhantomReferencee em ReferenceQueuevez disso.
Daniel Pryden
Eu quis dizer enquanto * obviamente, apesar de não te ter rebaixado. A resposta mais votada, no entanto, mostra o quão útil é o método, meio que invalidando seu argumento.
Pierre Arlaud
1
Mesmo no caso de pares nativos, acho que PhantomReferenceé uma solução melhor. Os finalizadores são uma verruga que sobrou dos primeiros dias de Java, e Object.clone()tipos like e raw, são parte da linguagem mais esquecida.
Daniel Pryden
1
Os perigos dos finalizadores são descritos de uma maneira mais acessível (compreensível pelos desenvolvedores) em C #. Porém, não se deve sugerir que os mecanismos subjacentes em Java e C # sejam os mesmos; não há nada em suas especificações que diga isso.
Rwong 02/07/2014
1

A Especificação de Linguagem Java (Java SE 7) declara:

Os finalizadores oferecem a chance de liberar recursos que não podem ser liberados automaticamente por um gerenciador de armazenamento automático. Nessas situações, a simples recuperação da memória usada por um objeto não garantiria que os recursos contidos nela fossem recuperados.

Benni
fonte