Por que você implementaria finalize ()?

371

Eu tenho lido muitas das perguntas novatas sobre Java finalize()e acho meio desconcertante que ninguém tenha deixado claro que finalize () é uma maneira não confiável de limpar recursos. Vi alguém comentar que eles o usam para limpar o Connections, o que é realmente assustador, pois a única maneira de chegar perto da garantia de que o Connection está fechado é implementar finalmente o try (catch).

Eu não tinha formação em CS, mas tenho programado profissionalmente em Java há quase uma década e nunca vi ninguém implementando finalize()em um sistema de produção. Isso ainda não significa que ele não tem seus usos ou que as pessoas com quem trabalhei estão fazendo certo.

Portanto, minha pergunta é: quais casos de uso existem para implementar finalize()que não podem ser tratados de maneira mais confiável por meio de outro processo ou sintaxe no idioma?

Forneça cenários específicos ou sua experiência, simplesmente repetir um livro de texto Java ou finalizar o uso pretendido não é suficiente, pois não é o objetivo desta pergunta.

Spencer Kormos
fonte
HI finalize () método muito bem explicado aqui howtodoinjava.com/2012/10/31/…
Sameer Kazi
2
A maioria dos códigos de aplicativos e bibliotecas nunca será usada finalize(). No entanto, o código da biblioteca da plataforma , como SocketInputStream, que gerencia recursos nativos em nome do responsável pela chamada, tenta minimizar o risco de vazamentos de recursos (ou usa mecanismos equivalentes, como os PhantomReferenceque foram adicionados posteriormente). Portanto, o ecossistema precisa deles , mesmo que 99,9999% dos desenvolvedores nunca escrevam um.
Brian Goetz

Respostas:

230

Você pode usá-lo como um pano de fundo para um objeto que contém um recurso externo (soquete, arquivo, etc.). Implemente um close()método e documento que precisa ser chamado.

Implemente finalize()para executar o close()processamento se você detectar que isso não foi feito. Talvez com algo despejado para stderrindicar que você está limpando depois de uma chamada de buggy.

Ele fornece segurança extra em uma situação excepcional / de buggy. Nem todo chamador sempre faz as try {} finally {}coisas corretas . Infelizmente, mas é verdade na maioria dos ambientes.

Concordo que raramente é necessário. E, como os comentaristas apontam, ele vem com a sobrecarga do GC. Use apenas se você precisar dessa segurança "cinto e suspensórios" em um aplicativo de longa duração.

Eu vejo que a partir do Java 9, Object.finalize()está obsoleta! Eles nos apontam java.lang.ref.Cleanere java.lang.ref.PhantomReferencecomo alternativas.

John M
fonte
44
Apenas certifique-se de que uma exceção nunca seja lançada por finalize (), ou o coletor de lixo não continuará a limpeza desse objeto e ocorrerá um vazamento de memória.
skaffman
17
A finalização não é garantida para ser chamada, portanto, não conte com a liberação de um recurso.
flicken
19
skaffman - Eu não acredito nisso (exceto por alguma implementação de JVM de buggy). No javadoc Object.finalize (): se uma exceção não capturada for lançada pelo método finalize, a exceção será ignorada e a finalização desse objeto será encerrada.
John M
4
Sua proposta pode ocultar os bugs das pessoas (sem fechar) por um longo tempo. Eu não recomendaria fazê-lo.
Kohlerm 28/11/08
64
Na verdade, eu usei finalize por esse motivo exato. Eu herdei o código e ele era muito problemático e tendia a deixar as conexões com o banco de dados abertas. Modificamos as conexões para conter dados sobre quando e onde elas foram criadas e, em seguida, implementamos a finalização para registrar essas informações se a conexão não foi fechada corretamente. Acabou sendo uma grande ajuda para rastrear as conexões não fechadas.
Brainimus
173

finalize()é uma dica para a JVM de que pode ser bom executar seu código em um momento não especificado. Isso é bom quando você deseja que o código falhe misteriosamente na execução.

Fazer algo significativo nos finalizadores (basicamente qualquer coisa, exceto o log) também é bom em três situações:

  • você quer apostar que outros objetos finalizados ainda estarão em um estado que o restante do seu programa considere válido.
  • você deseja adicionar muito código de verificação a todos os métodos de todas as suas classes que possuem um finalizador, para garantir que eles se comportem corretamente após a finalização.
  • você deseja ressuscitar acidentalmente os objetos finalizados e gastar muito tempo tentando descobrir por que eles não funcionam e / ou por que eles não são finalizados quando são liberados.

Se você acha que precisa finalizar (), às vezes o que você realmente deseja é uma referência fantasma (que no exemplo dado pode conter uma referência rígida a uma conexão usada por seu referendo e fechá-la depois que a referência fantasma estiver na fila). Isso também tem a propriedade de que misteriosamente nunca pode ser executado, mas pelo menos não pode chamar métodos ou ressuscitar objetos finalizados. Portanto, é ideal para situações em que você não precisa absolutamente fechar essa conexão de maneira limpa, mas gostaria bastante, e os clientes da sua classe não podem ou não querem se fechar (o que é realmente justo o suficiente - o que' é o ponto de ter um coletor de lixo, se você criar interfaces que exijam uma ação específica antes da coleta? Isso apenas nos coloca de volta aos dias de malloc / free.)

Outras vezes, você precisa do recurso que acha que está gerenciando para ser mais robusto. Por exemplo, por que você precisa fechar essa conexão? No fim das contas, ele deve se basear em algum tipo de E / S fornecida pelo sistema (soquete, arquivo, qualquer que seja). Por que você não pode confiar no sistema para fechá-lo quando o nível mais baixo de recursos é determinado? Se o servidor na outra extremidade exigir absolutamente que você feche a conexão de maneira limpa, em vez de apenas soltar o soquete, o que acontecerá quando alguém tropeçar no cabo de alimentação da máquina em que seu código está sendo executado ou quando a rede intermediária for desativada?

Isenção de responsabilidade: trabalhei em uma implementação da JVM no passado. Eu odeio finalizadores.

Steve Jessop
fonte
76
Promovendo a extrema justiça do sarcasmo.
Percepção
32
Isso não responde à pergunta; apenas diz todas as desvantagens de, finalize()sem dar nenhuma razão para alguém realmente querer usá-lo.
Caracol mecânico
11
@supercat: referências fantasma (para essa matéria todas as referências) foram introduzidos no Java 2
Steve Jessop
11
@ supercat: desculpe sim, por "referências" eu quis dizer java.lang.ref.Referencee suas subclasses. O Java 1 tinha variáveis ​​:-) E sim, também tinha finalizadores.
21713 Steve Steveop
2
@SteveJessop: Se um construtor de classe base solicitar que outras entidades alterem seu estado em nome do objeto em construção (um padrão muito comum), mas um inicializador de campo de classe derivada lança uma exceção, o objeto parcialmente construído deve limpar imediatamente depois de si mesmo (ou seja, notificar as entidades externas de que seus serviços não são mais necessários), mas nem o Java nem o .NET oferecem nenhum padrão limpo remotamente, pelo qual isso pode acontecer. Na verdade, eles parecem se esforçar para dificultar uma referência à coisa que precisa de limpeza para alcançar o código de limpeza.
Supercat
57

Uma regra simples: nunca use finalizadores.

Somente o fato de um objeto ter um finalizador (independentemente do código que ele executa) é suficiente para causar uma sobrecarga considerável na coleta de lixo.

De um artigo de Brian Goetz:

Objetos com finalizadores (aqueles que possuem um método finalize () não trivial) têm uma sobrecarga significativa em comparação com objetos sem finalizadores e devem ser usados ​​com moderação. Objetos finalizáveis ​​são mais lentos para alocar e mais lentos para coletar. No momento da alocação, a JVM deve registrar quaisquer objetos finalizáveis ​​no coletor de lixo e (pelo menos na implementação da JVM HotSpot) os objetos finalizáveis ​​devem seguir um caminho de alocação mais lento do que a maioria dos outros objetos. Da mesma forma, os objetos finalizáveis ​​também são mais lentos para coletar. Leva pelo menos dois ciclos de coleta de lixo (na melhor das hipóteses) antes que um objeto finalizável possa ser recuperado e o coletor de lixo precisa fazer um trabalho extra para chamar o finalizador. O resultado é mais tempo gasto alocando e coletando objetos e mais pressão no coletor de lixo, porque a memória usada por objetos finalizáveis ​​inacessíveis é retida por mais tempo. Combine isso com o fato de que os finalizadores não são garantidos para execução em qualquer período de tempo previsível, ou mesmo em todos, e você pode ver que existem relativamente poucas situações para as quais a finalização é a ferramenta certa a ser usada.

Tom
fonte
46

A única vez que usei finalizar no código de produção foi implementar uma verificação de que os recursos de um determinado objeto foram limpos e, se não, registrar uma mensagem muito vocal. Na verdade, ele não tentou fazer isso sozinho, apenas gritou muito se não fosse feito corretamente. Acabou por ser bastante útil.

skaffman
fonte
34

Trabalho em Java profissionalmente desde 1998 e nunca o implementei finalize(). Nem uma vez.

Paul Tomblin
fonte
Como destruo o objeto de classe pública então? Está causando problemas no ADF. Basicamente, é suposto recarregar a página (obtendo dados atualizados de uma API), mas está pegando o objeto antigo e mostrando os resultados
InfantPro'Aravind '
2
A chamada @ InfantPro'Aravind 'finalize não remove o objeto. É um método que você implementa que a JVM pode optar por chamar se e quando o lixo coleta seu objeto.
Paul Tomblin
@PaulTomblin, obrigado por esse detalhe. Alguma sugestão de atualizar uma página no ADF?
InfantPro'Aravind '
@ InfantPro'Aravind 'Não faço ideia, nunca usei o ADF.
Paul Tomblin
28

A resposta aceita é boa, eu só queria acrescentar que agora existe uma maneira de ter a funcionalidade de finalizar sem realmente usá-la.

Veja as classes "Referência". Referência fraca, Referência fantasma e Referência suave.

Você pode usá-los para manter uma referência a todos os seus objetos, mas essa referência SOZINHO não interromperá o GC. O mais interessante sobre isso é que você pode chamar um método quando ele será excluído, e pode-se garantir que esse método seja chamado.

Quanto à finalização: usei finalizar uma vez para entender quais objetos estavam sendo liberados. Você pode jogar alguns jogos legais com estática, contagem de referência e outras coisas - mas foi apenas para análise, mas cuidado com códigos como este (não apenas na finalização, mas é onde você provavelmente verá):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

É um sinal de que alguém não sabia o que estava fazendo. "Limpar" assim nunca é praticamente necessário. Quando a classe é submetida ao GC, isso é feito automaticamente.

Se você encontrar um código como esse em uma finalização, é garantido que a pessoa que o escreveu estava confusa.

Se estiver em outro lugar, pode ser que o código seja um patch válido para um modelo ruim (uma classe permanece por um longo tempo e, por algum motivo, as coisas referenciadas precisavam ser liberadas manualmente antes que o objeto fosse submetido ao GC). Geralmente, é porque alguém se esqueceu de remover um ouvinte ou algo assim e não consegue entender por que o objeto não está sendo usado no GC, para apenas excluir as coisas a que se refere e encolher os ombros e ir embora.

Nunca deve ser usado para limpar as coisas "Mais rápido".

Bill K
fonte
3
Referências fantasmas são a melhor maneira de acompanhar quais objetos estão sendo liberados, eu acho.
Bill Michell
2
voto positivo por fornecer a melhor explicação para "referência fraca" que eu já ouvi.
Sscarduzio #
Lamento que isso tenha me levado muitos anos para ler isso, mas é realmente uma boa adição às respostas.
Spencer Kormos
27

Não sei bem o que você pode fazer disso, mas ...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Então, acho que o Sol encontrou alguns casos em que (eles acham) que deveriam ser usados.

itsadok
fonte
12
Eu acho que também é uma questão de "O desenvolvedor da Sun sempre estará certo"?
precisa saber é o seguinte
13
Mas quantos desses usos estão apenas implementando ou testando o suporte, em finalizevez de realmente usá-lo?
Caracol mecânico
Eu uso o Windows. Como você faria a mesma coisa no Windows?
gparyani
2
@damryfbfnetsi: Tente instalar o ack ( beyondgrep.com ) por meio de chocolatey.org/packages/ack . Ou use um recurso simples Localizar em arquivos no $FAVORITE_IDE.
Brian Cline
21
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

====================================

resultado:

This is finalize

MyObject still alive!

=======================================

Portanto, você pode tornar uma instância inacessível acessível no método finalize.

Kiki
fonte
21
Por alguma razão, esse código me faz pensar em filmes de zumbis. Você GC seu objeto e, em seguida, levanta-se de novo ...
steveha
acima são bons códigos. Eu queria saber como tornar o objeto acessível novamente novamente agora.
Lwpro2
15
não, acima há códigos ruins. é um exemplo de por que finalizar é ruim.
Tony
Lembre-se de que invocar System.gc();não é uma garantia de que o coletor de lixo realmente será executado. É critério total do GC limpar o heap (talvez ainda haja heap livre suficiente, talvez não muito fragmentado ...). Portanto, é apenas um humilde pedido do programador "por favor, você poderia me ouvir e sair correndo?" e não um comando "corra agora como eu digo!". Desculpe por usar palavras linguais, mas diz exatamente como o GC da JVM funciona.
Roland
11

finalize()pode ser útil para detectar vazamentos de recursos. Se o recurso precisar ser fechado, mas não for gravado, o fato de não ter sido fechado em um arquivo de log e fechá-lo. Dessa forma, você remove o vazamento de recursos e oferece a si mesmo uma maneira de saber que isso aconteceu para que você possa corrigi-lo.

Estou programando em Java desde 1.0 alpha 3 (1995) e ainda tenho que substituir a finalização por qualquer coisa ...

TofuBeer
fonte
6

Você não deve depender de finalize () para limpar seus recursos para você. finalize () não será executado até que a classe seja coletada como lixo, se for o caso. É muito melhor liberar explicitamente recursos quando você terminar de usá-los.

Bill the Lizard
fonte
5

Para destacar um ponto nas respostas acima: os finalizadores serão executados no único thread do GC. Ouvi falar de uma grande demonstração da Sun, na qual os desenvolvedores dormiram um pouco em alguns finalizadores e intencionalmente trouxeram uma demo 3D extravagante de joelhos.

É melhor evitar, com possível exceção dos diagnósticos do ambiente de teste.

O pensamento de Eckel em Java tem uma boa seção sobre isso.

Michael Easter
fonte
4

Hmmm, uma vez eu o usei para limpar objetos que não estavam sendo devolvidos a um pool existente.

Eles foram passados ​​muito, por isso era impossível dizer quando eles poderiam ser devolvidos com segurança para a piscina. O problema era que ele introduziu uma penalidade enorme durante a coleta de lixo, muito maior do que qualquer economia com o agrupamento de objetos. Estava em produção por cerca de um mês antes de eu arrancar toda a piscina, tornar tudo dinâmico e acabar com ela.

Hearen
fonte
4

Tenha cuidado com o que você faz em um finalize(). Especialmente se você o estiver usando para coisas como ligar para close () para garantir que os recursos sejam limpos. Tivemos várias situações em que tínhamos bibliotecas JNI vinculadas ao código java em execução e, em qualquer circunstância em que usamos finalize () para invocar métodos JNI, teríamos uma corrupção muito ruim da pilha de java. A corrupção não foi causada pelo próprio código JNI subjacente; todos os rastreamentos de memória estavam corretos nas bibliotecas nativas. Era justamente o fato de estarmos chamando os métodos JNI de finalize ().

Isso ocorreu com o JDK 1.5, que ainda é amplamente utilizado.

Não descobrimos que algo deu errado até muito mais tarde, mas, no final, o culpado sempre foi o método finalize () usando chamadas JNI.

Steven M. Cherry
fonte
3

Ao escrever um código que será usado por outros desenvolvedores que exija que algum tipo de método de "limpeza" seja chamado para liberar recursos. Às vezes, esses outros desenvolvedores esquecem de chamar seu método de limpeza (ou fechamento, destruição ou o que seja). Para evitar possíveis vazamentos de recursos, você pode verificar o método finalize para garantir que o método foi chamado e, caso contrário, você mesmo pode chamá-lo.

Muitos drivers de banco de dados fazem isso em suas implementações de Declaração e Conexão para fornecer um pouco de segurança contra os desenvolvedores que se esquecem de encerrar a conversa.

John Meagher
fonte
2

Edit: Ok, realmente não funciona. Eu o implementei e pensei que, se às vezes falhar, tudo bem para mim, mas ele nem sequer chamou o método finalize uma única vez.

Não sou programador profissional, mas no meu programa tenho um caso que acho que é um exemplo de bom uso de finalize (), que é um cache que grava seu conteúdo no disco antes de ser destruído. Como não é necessário que seja executado todas as vezes na destruição, isso apenas acelera o meu programa, espero que não tenha feito errado.

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}
user265243
fonte
Eu acho que um cache (do tipo que você liberaria para o disco) é um bom exemplo. E mesmo que finalize não seja chamado, não se preocupe.
foo
2

Pode ser útil remover itens que foram adicionados a um local global / estático (por necessidade) e precisam ser removidos quando o objeto é removido. Por exemplo:

    private void addGlobalClickListener () {
        weakAwtEventListener = new WeakAWTEventListener (this);

        Toolkit.getDefaultToolkit (). AddAWTEventListener (fracoAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Sobrepor
    void protegido finalize () lança Throwable {
        super.finalize ();

        if (weakAwtEventListener! = null) {
            Toolkit.getDefaultToolkit (). RemoveAWTEventListener (weakAwtEventListener);
        }
    }
Alexander Malfait
fonte
Isso requer esforços adicionais, pois quando o ouvinte tem uma forte referência à thisinstância passada a ele no construtor, essa instância nunca se torna inacessível, portanto finalize(), nunca será limpa de qualquer maneira. Se, por outro lado, o ouvinte mantiver uma referência fraca a esse objeto, como o nome sugere, esse construto corre o risco de ser limpo às vezes em que não deveria. Os ciclos de vida do objeto não são a ferramenta certa para implementar a semântica do aplicativo.
Holger
0

iirc - você pode usar o método finalize como um meio de implementar um mecanismo de agrupamento para recursos caros - para que eles não obtenham GCs também.

JGFMK
fonte
0

Como uma nota rodapé:

Um objeto que substitui finalize () é tratado especialmente pelo coletor de lixo. Geralmente, um objeto é destruído imediatamente durante o ciclo de coleta, depois que o objeto não está mais no escopo. No entanto, objetos finalizáveis ​​são movidos para uma fila, onde threads de finalização separados drenam a fila e executam o método finalize () em cada objeto. Quando o método finalize () terminar, o objeto estará finalmente pronto para a coleta de lixo no próximo ciclo.

Fonte: finalize () descontinuado em java-9

Sudip Bhandari
fonte
0

Os recursos (Arquivo, Soquete, Fluxo etc.) precisam ser fechados assim que terminarmos com eles. Eles geralmente têm um close()método que geralmente chamamos na finallyseção de try-catchdeclarações. Às vezes, finalize()também pode ser usado por poucos desenvolvedores, mas a IMO não é uma maneira adequada, pois não há garantia de que finalize será chamado sempre.

No Java 7, temos a instrução try-with-resources , que pode ser usada como:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

No exemplo acima, o try-with-resource fechará o recurso automaticamente BufferedReader, invocando o close()método Se quisermos, também podemos implementar o Closeable em nossas próprias classes e usá-lo de maneira semelhante. OMI parece mais arrumado e simples de entender.

akhil_mittal
fonte
0

Pessoalmente, quase nunca usei, finalize()exceto em uma circunstância rara: fiz uma coleção personalizada do tipo genérico e escrevi um finalize()método personalizado que faz o seguinte:

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

( CompleteObjectÉ uma interface que eu fiz, que permite especificar que você implementou raramente implementados Objectmétodos como #finalize(), #hashCode(), e #clone())

Portanto, usando um #setDestructivelyFinalizes(boolean)método irmão , o programa que usa minha coleção pode (ajudar) garantir que a destruição de uma referência a essa coleção também destrua referências a seu conteúdo e descarte todas as janelas que possam manter a JVM ativa sem intenção. Também considerei interromper qualquer thread, mas isso abriu uma nova lata de worms.

Supuhstar
fonte
4
A chamada finalize()de suas CompleteObjectinstâncias, como você faz no código acima, implica que ela pode ser chamada duas vezes, pois a JVM ainda pode invocar finalize()quando esses objetos se tornarem inacessíveis, o que, devido à lógica da finalização, pode estar anterior ao finalize()método de sua coleção especial é chamado ou até simultaneamente ao mesmo tempo. Desde que eu não vejo quaisquer medidas para garantir a segurança do thread em seu método, você parece não ter consciência disso ...
Holger
0

A resposta aceita lista que o fechamento de um recurso durante a finalização pode ser feito.

No entanto, esta resposta mostra que, pelo menos no java8 com o compilador JIT, você enfrenta problemas inesperados nos quais, às vezes, o finalizador é chamado antes mesmo de terminar a leitura de um fluxo mantido pelo seu objeto.

Portanto, mesmo nessa situação, chamar finalize não seria recomendado.

Joeblade
fonte
Funcionaria se a operação fosse tornada segura para threads, o que é obrigatório, pois os finalizadores podem ser chamados por threads arbitrários. Como a segurança do encadeamento implementada corretamente implica que não apenas a operação de fechamento, mas também as operações que usam o recurso devem ser sincronizadas no mesmo objeto ou bloqueio, haverá uma relação de antes do acontecimento entre o uso e a operação de fechamento, o que também evita que seja muito cedo. finalização. Mas, é claro, a um preço alto para todo o uso ...
Holger