Perché mai implementare finalize ()?


371

Ho letto molte delle domande principianti su Java finalize()e trovo sconcertante che nessuno abbia chiarito che finalizzare () è un modo inaffidabile per ripulire le risorse. Ho visto qualcuno commentare che lo usano per ripulire Connections, il che è davvero spaventoso poiché l'unico modo per avvicinarsi alla garanzia che una connessione sia chiusa è implementare try (catch) finalmente.

Non avevo studiato in CS, ma ho programmato in Java professionalmente per quasi un decennio ormai e non ho mai visto nessuno implementare finalize() in un sistema di produzione mai. Ciò non significa ancora che non abbia i suoi usi o che le persone con cui ho lavorato lo stiano facendo bene.

Quindi la mia domanda è: quali casi d'uso ci sono per l'implementazione finalize() che non possono essere gestiti in modo più affidabile tramite un altro processo o sintassi all'interno della lingua?

Fornisci scenari specifici o la tua esperienza, semplicemente ripetere un libro di testo Java o finalizzare l'uso previsto non è sufficiente, in quanto non è l'intento di questa domanda.


Metodo HI finalize () molto ben spiegato qui howtodoinjava.com/2012/10/31/…
Sameer Kazi

2
La maggior parte del codice dell'applicazione e della libreria non utilizzerà mai finalize(). Tuttavia, il codice della libreria della piattaforma , come SocketInputStream, che gestisce le risorse native per conto del chiamante, lo fa per cercare di ridurre al minimo il rischio di perdite di risorse (o utilizza meccanismi equivalenti, come quelli PhantomReferenceche sono stati aggiunti in seguito.) Quindi l'ecosistema ne ha bisogno , anche se il 99,9999% degli sviluppatori non ne scriverà mai uno.
Brian Goetz,

Risposte:


230

È possibile utilizzarlo come backstop per un oggetto che contiene una risorsa esterna (socket, file, ecc.). Implementare un close()metodo e documentare che deve essere chiamato.

Implementare finalize()per eseguire l' close()elaborazione se si rileva che non è stata eseguita. Forse con qualcosa di scaricatostderr sottolineare che stai pulendo dopo un chiamante buggy.

Fornisce ulteriore sicurezza in una situazione eccezionale / buggy. Non tutti i chiamanti faranno il correttotry {} finally {} cose . Sfortunato, ma vero nella maggior parte degli ambienti.

Sono d'accordo che raramente è necessario. E come sottolineano i commentatori, viene fornito con GC overhead. Utilizzare solo se è necessaria la sicurezza di "cintura e bretelle" in un'app di lunga durata.

Vedo che a partire da Java 9, Object.finalize()è obsoleto! Ci indicano java.lang.ref.Cleanere java.lang.ref.PhantomReferencecome alternative.


44
Assicurati solo che non venga mai generata un'eccezione da finalize (), altrimenti Garbage Collector non continuerà la pulizia di quell'oggetto e otterrai una perdita di memoria.
Skaffman,

17
Finalize non è garantito per essere chiamato, quindi non contare su di esso rilasciando una risorsa.
sfarfallio

19
skaffman - Non ci credo (a parte qualche implementazione JVM buggy). Da Object.finalize () javadoc: se un'eccezione non rilevata viene generata dal metodo finalize, l'eccezione viene ignorata e la finalizzazione di quell'oggetto termina.
John M,

4
La tua proposta può nascondere i bug delle persone (non chiamare vicino) per molto tempo. Non consiglierei di farlo.
kohlerm,

64
In realtà ho usato finalizzare per questo motivo esatto. Ho ereditato il codice ed era molto difettoso e avevo la tendenza a lasciare aperte le connessioni al database. Abbiamo modificato le connessioni per contenere i dati su quando e dove sono state create e quindi implementato finalizzare per registrare queste informazioni se la connessione non è stata chiusa correttamente. Si è rivelato di grande aiuto per rintracciare le connessioni non chiuse.
brainimus,

173

finalize()è un suggerimento per JVM che potrebbe essere utile eseguire il codice in un momento non specificato. Questo è utile quando si desidera che il codice non funzioni in modo misterioso.

Fare qualcosa di significativo nei finalizzatori (praticamente qualsiasi cosa tranne la registrazione) va bene anche in tre situazioni:

  • vuoi scommettere che altri oggetti finalizzati saranno ancora in uno stato che il resto del tuo programma considera valido.
  • vuoi aggiungere un sacco di codice di controllo a tutti i metodi di tutte le tue classi che hanno un finalizzatore, per assicurarti che si comportino correttamente dopo la finalizzazione.
  • vuoi resuscitare accidentalmente oggetti finalizzati e spendere molto tempo a cercare di capire perché non funzionano e / o perché non vengono finalizzati quando vengono rilasciati.

Se ritieni di aver bisogno di finalizzare (), a volte ciò che realmente vuoi è un riferimento fantasma (che nell'esempio fornito potrebbe contenere un riferimento reale a una connessione utilizzata dal suo riferimento, e chiuderlo dopo che il riferimento fantasma è stato messo in coda). Questo ha anche la proprietà che potrebbe non funzionare misteriosamente, ma almeno non può richiamare metodi o resuscitare oggetti finalizzati. Quindi è giusto per le situazioni in cui non hai assolutamente bisogno di chiudere in modo pulito quella connessione, ma ti piacerebbe molto, e i clienti della tua classe non possono o non vogliono chiudere da soli (il che è abbastanza giusto - che cosa' s il punto di avere un garbage collector se si progettano interfacce che richiedono un'azione specifica prima della raccolta? Questo ci riporta ai tempi di malloc / free.)

Altre volte hai bisogno della risorsa che pensi di riuscire a essere più robusta. Ad esempio, perché è necessario chiudere quella connessione? Alla fine deve essere basato su un qualche tipo di I / O fornito dal sistema (socket, file, qualunque cosa), quindi perché non puoi fare affidamento sul sistema per chiuderlo quando hai il livello più basso di risorse? Se il server all'altra estremità richiede assolutamente di chiudere la connessione in modo pulito piuttosto che far cadere la presa, cosa accadrà quando qualcuno inciamperà sul cavo di alimentazione della macchina su cui è in esecuzione il codice o la rete che interviene?

Disclaimer: ho lavorato su un'implementazione JVM in passato. Odio i finalizzatori.


76
Riconoscimento per giustizia estrema sarcasmo.
Percezione

32
Questo non risponde alla domanda; dice solo tutti gli svantaggi di finalize()senza dare ragioni per cui qualcuno vorrebbe davvero usarlo.
Lumaca meccanica,

1
@supercat: i riferimenti fantasma (del resto tutti i riferimenti) sono stati introdotti in Java 2
Steve Jessop

1
@supercat: scusa sì, per "riferimenti" intendevo java.lang.ref.Referencee le sue sottoclassi. Java 1 aveva variabili :-) E sì, aveva anche i finalizzatori.
Steve Jessop,

2
@SteveJessop: se un costruttore di classe base chiede ad altre entità di modificare il proprio stato per conto dell'oggetto in costruzione (un modello molto comune), ma un inizializzatore di campo di classe derivata genera un'eccezione, l'oggetto parzialmente costruito dovrebbe immediatamente ripulire dopo se stesso (ovvero notificare a tali entità esterne che i loro servizi non sono più richiesti), ma né Java né .NET offrono alcun modello uniformemente pulito da remoto attraverso il quale ciò può accadere. In effetti, sembrano fare di tutto per rendere difficile il riferimento a qualcosa che necessita di pulizia per raggiungere il codice di pulizia.
supercat,

57

Una semplice regola: non usare mai i finalizzatori.

Il solo fatto che un oggetto abbia un finalizzatore (indipendentemente dal codice che esegue) è sufficiente per causare un notevole sovraccarico per la garbage collection.

Da un articolo di Brian Goetz:

Gli oggetti con finalizzatori (quelli che hanno un metodo finalize () non banale) hanno un notevole sovraccarico rispetto agli oggetti senza finalizzatori e dovrebbero essere usati con parsimonia. Gli oggetti finalizzabili sono sia più lenti da allocare sia più lenti da raccogliere. Al momento dell'allocazione, la JVM deve registrare tutti gli oggetti finalizzabili con il Garbage Collector e (almeno nell'implementazione di HotSpot JVM) gli oggetti finalizzabili devono seguire un percorso di allocazione più lento rispetto alla maggior parte degli altri oggetti. Allo stesso modo, anche gli oggetti finalizzabili sono più lenti da raccogliere. Ci vogliono almeno due cicli di garbage collection (nel migliore dei casi) prima che un oggetto finalizzabile possa essere recuperato e il garbage collector deve fare un lavoro extra per invocare il finalizzatore. Il risultato è più tempo dedicato all'allocazione e alla raccolta di oggetti e una maggiore pressione sul Garbage Collector, poiché la memoria utilizzata da oggetti finalizzabili non raggiungibili viene mantenuta più a lungo. Combinalo con il fatto che i finalizzatori non sono garantiti per essere eseguiti in tempi prevedibili, o addirittura affatto, e puoi vedere che ci sono relativamente poche situazioni per le quali la finalizzazione è lo strumento giusto da usare.


46

L'unica volta che ho usato finalizzare nel codice di produzione era implementare un controllo che le risorse di un determinato oggetto fossero state ripulite e, in caso contrario, registrare un messaggio molto vocale. In realtà non ha provato a farlo da solo, ha solo gridato molto se non è stato fatto correttamente. Si è rivelato abbastanza utile.


34

Faccio Java professionalmente dal 1998 e non l'ho mai implementato finalize(). Non una volta.


Come posso quindi distruggere l'oggetto di classe pubblica? Sta causando problemi nell'ADF. Fondamentalmente si suppone di ricaricare la pagina (ottenere nuovi dati da un'API), ma sta prendendo il vecchio oggetto e mostrando i risultati
InfantPro'Aravind '

2
La chiamata di finalizzazione di @Allind 'InfantPro non rimuove l'oggetto. È un metodo implementato che JVM può scegliere di chiamare se e quando viene immesso nel cestino.
Paul Tomblin

@PaulTomblin, grazie per questo dettaglio. Qualche suggerimento per aggiornare una pagina sull'ADF?
InfantPro'Aravind '

@ InfantPro'Aravind 'Nessuna idea, non ho mai usato l'ADF.
Paul Tomblin

28

La risposta accettata è buona, volevo solo aggiungere che ora c'è un modo per avere la funzionalità di finalizzazione senza effettivamente usarla affatto.

Guarda le classi "Riferimenti". Riferimento debole, riferimento Phantom e riferimento morbido.

Puoi usarli per mantenere un riferimento a tutti i tuoi oggetti, ma questo riferimento ALONE non fermerà GC. La cosa bella di questo è che puoi averlo chiamare un metodo quando verrà eliminato, e questo metodo può essere garantito per essere chiamato.

Per quanto riguarda finalizzare: ho usato finalizzare una volta per capire quali oggetti venivano liberati. Puoi giocare ad alcuni giochi accurati con statica, conteggio dei riferimenti e simili - ma era solo per analisi, ma fai attenzione a codice come questo (non solo per finalizzare, ma è lì che è più probabile vederlo):

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

È un segno che qualcuno non sapeva cosa stavano facendo. "Pulire" in questo modo non è praticamente mai necessario. Quando la classe è GC'd, questo viene fatto automaticamente.

Se trovi un codice del genere in una finalizzazione, è garantito che la persona che l'ha scritto era confusa.

Se è altrove, è possibile che il codice sia una patch valida per un modello errato (una classe rimane a lungo in circolazione e per qualche ragione le cose a cui faceva riferimento dovevano essere liberate manualmente prima che l'oggetto fosse GC'd). Generalmente è perché qualcuno ha dimenticato di rimuovere un ascoltatore o qualcosa del genere e non riesce a capire perché il suo oggetto non sia GC'd, quindi cancellano semplicemente le cose a cui si riferisce e scrollano le spalle e se ne vanno.

Non dovrebbe mai essere usato per ripulire le cose "Più veloce".


3
I riferimenti fantasma sono un modo migliore per tenere traccia di quali oggetti vengono liberati, credo.
Bill Michell

2
voto per fornire la migliore spiegazione di "riferimento debole" che abbia mai sentito.
sscarduzio,

Mi dispiace che questo abbia impiegato così tanti anni a leggerlo, ma è davvero una bella aggiunta alle risposte.
Spencer Kormos,

27

Non sono sicuro di cosa tu possa fare di questo, ma ...

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

Quindi immagino che il Sole abbia trovato alcuni casi in cui (pensano) che dovrebbe essere usato.


12
Immagino sia anche una domanda di "Lo sviluppatore Sun avrà sempre ragione"?
CodeReaper

13
Ma quanti di questi usi stanno semplicemente implementando o testando il supporto finalizeanziché utilizzarlo effettivamente?
Lumaca meccanica il

Io uso Windows. Come faresti la stessa cosa in Windows?
gparyani,

2
@damryfbfnetsi: prova a installare ack ( beyondgrep.com ) tramite chocolatey.org/packages/ack . Oppure utilizza una semplice funzione Trova nei file in $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!");  
    }
}

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

risultato:

This is finalize

MyObject still alive!

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

Quindi puoi rendere raggiungibile un'istanza irraggiungibile nel metodo finalize.


21
Per qualche ragione, questo codice mi fa pensare ai film di zombi. GC il tuo oggetto e poi si alza di nuovo ...
steveha,

sopra è buoni codici. Mi chiedevo come rendere di nuovo raggiungibile l'oggetto nuovamente.
lwpro2,

15
no, sopra sono codici errati. è un esempio del perché finalizzare è negativo.
Tony,

Ricorda che invocare System.gc();non è una garanzia che il Garbage Collector verrà effettivamente eseguito. È la piena discrezione del GC per ripulire l'heap (forse ancora abbastanza heap gratuito in giro, forse non frammentato troppo, ...). Quindi è solo un'umile richiesta da parte del programmatore "per favore, potresti ascoltarmi ed eseguire?" e non un comando "esegui ora come ho detto!". Ci scusiamo per l'uso di parole linguali ma dice esattamente come funziona il GC della JVM.
Roland,

11

finalize()può essere utile per rilevare perdite di risorse. Se la risorsa deve essere chiusa ma non è scritto, non essere chiusa in un file di registro e chiuderla. In questo modo rimuovi la perdita di risorse e ti dai un modo per sapere che è successo in modo da poterlo riparare.

Sto programmando in Java dalla 1.0 alpha 3 (1995) e non ho ancora finito di finalizzare nulla ...


6

Non dovresti dipendere da finalize () per ripulire le tue risorse. finalize () non funzionerà fino a quando la classe non sarà raccolta in modo inutile, se allora. È molto meglio liberare esplicitamente le risorse quando hai finito di usarle.


5

Per evidenziare un punto nelle risposte precedenti: i finalizzatori verranno eseguiti sul thread GC singolo. Ho sentito parlare di una grande demo di Sun in cui gli sviluppatori hanno aggiunto un po 'di sonno ad alcuni finalizzatori e hanno intenzionalmente messo in ginocchio una demo 3D altrimenti stravagante.

Meglio evitare, con la possibile eccezione della diagnostica test-env.

Il pensiero di Eckel in Java ha una buona sezione su questo.


4

Hmmm, una volta l'ho usato per ripulire oggetti che non venivano restituiti a un pool esistente.

Erano passati molto, quindi era impossibile dire quando potevano essere tranquillamente riportati in piscina. Il problema era che ha introdotto un'enorme penalità durante la raccolta dei rifiuti che era molto maggiore di qualsiasi risparmio derivante dalla messa in comune degli oggetti. È stato in produzione per circa un mese prima che strappassi l'intero pool, rendessi tutto dinamico e ci fossi finito.


4

Fai attenzione a ciò che fai in a finalize(). Soprattutto se lo stai usando per cose come chiamare close () per assicurarti che le risorse vengano ripulite. Ci siamo imbattuti in diverse situazioni in cui avevamo librerie JNI collegate al codice java in esecuzione, e in qualsiasi circostanza in cui abbiamo usato finalize () per invocare metodi JNI, avremmo avuto una pessima corruzione dell'heap java. La corruzione non è stata causata dallo stesso codice JNI sottostante, tutte le tracce di memoria andavano bene nelle librerie native. Era solo il fatto che stavamo chiamando i metodi JNI da finalize ().

Questo è stato con un JDK 1.5 che è ancora ampiamente utilizzato.

Non scopriremmo che qualcosa è andato storto molto più tardi, ma alla fine il colpevole era sempre il metodo finalize () che utilizzava le chiamate JNI.


3

Quando si scrive codice che verrà utilizzato da altri sviluppatori che richiede una sorta di metodo di "pulizia" da chiamare per liberare risorse. A volte quegli altri sviluppatori dimenticano di chiamare il metodo di pulizia (o chiusura, distruzione o altro). Per evitare possibili perdite di risorse è possibile verificare il metodo finalize per assicurarsi che il metodo sia stato chiamato e, in caso contrario, è possibile chiamarlo da soli.

Molti driver di database fanno questo nelle loro implementazioni di Dichiarazione e Connessione per fornire un po 'di sicurezza agli sviluppatori che dimenticano di chiamarli vicini.


2

Modifica: Okay, davvero non funziona. L'ho implementato e ho pensato che se fallisce a volte va bene per me, ma non ha nemmeno chiamato il metodo finalize una sola volta.

Non sono un programmatore professionista, ma nel mio programma ho un caso che penso sia un esempio di un buon caso di utilizzo di finalize (), ovvero una cache che scrive il suo contenuto sul disco prima che venga distrutto. Poiché non è necessario che venga eseguito ogni volta in caso di distruzione, accelera solo il mio programma, spero che non abbia sbagliato.

@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);
}

Penso che una cache (del tipo da scaricare su disco) sia un buon esempio. E anche se la finalizzazione non viene chiamata, nessun sudore.
foo

2

Può essere utile rimuovere le cose che sono state aggiunte a un luogo globale / statico (per necessità) e devono essere rimosse quando l'oggetto viene rimosso. Per esempio:

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

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

    @Oltrepassare
    protetto void finalize () genera Throwable {
        super.finalize ();

        if (weakAwtEventListener! = null) {
            . Toolkit.getDefaultToolkit () removeAWTEventListener (weakAwtEventListener);
        }
    }

Ciò ha bisogno di ulteriori sforzi, come quando l'ascoltatore ha un forte riferimento thisall'istanza che gli è stata passata nel costruttore, quell'istanza non diventerà mai irraggiungibile, quindi finalize()non pulirà mai comunque. Se, d'altra parte, l'ascoltatore ha un debole riferimento a quell'oggetto, come suggerisce il nome, questo costrutto rischia di essere ripulito nei momenti in cui non dovrebbe. I cicli di vita degli oggetti non sono lo strumento giusto per implementare la semantica delle applicazioni.
Holger

0

iirc - puoi usare il metodo finalize come mezzo per implementare un meccanismo di pooling per risorse costose - in modo che non ottengano anche i GC.


0

Come nota a margine:

Un oggetto che sostituisce finalize () viene trattato in modo speciale dal Garbage Collector. Di solito, un oggetto viene immediatamente distrutto durante il ciclo di raccolta dopo che l'oggetto non rientra più nell'ambito. Tuttavia, gli oggetti finalizzabili vengono invece spostati in una coda, in cui thread di finalizzazione separati svuoteranno la coda ed eseguiranno il metodo finalize () su ciascun oggetto. Una volta terminato il metodo finalize (), l'oggetto sarà finalmente pronto per la garbage collection nel ciclo successivo.

Fonte: finalize () deprecato su java-9


0

Le risorse (File, Socket, Stream ecc.) Devono essere chiuse una volta terminato. Generalmente hanno un close()metodo che generalmente chiamiamo nella finallysezione delle try-catchdichiarazioni. A volte finalize()può essere utilizzato anche da pochi sviluppatori, ma IMO non è un modo adatto in quanto non esiste alcuna garanzia che finalizzare verrà chiamato sempre.

In Java 7 abbiamo un'istruzione try-with-resources che può essere utilizzata come:

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.
}

Nell'esempio precedente try-with-resource chiuderà automaticamente la risorsa BufferedReaderinvocando il close()metodo. Se vogliamo, possiamo anche implementare Closeable nelle nostre classi e usarlo in modo simile. IMO sembra più pulito e semplice da capire.


0

Personalmente, non l'ho quasi mai usato, finalize()tranne in una rara circostanza: ho creato una raccolta personalizzata di tipo generico e ho scritto un finalize()metodo personalizzato che procede come segue:

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È un'interfaccia che ho fatto che consente di specificare che hai implementato raramente attuati Objectmetodi come #finalize(), #hashCode(), e #clone())

Quindi, usando un #setDestructivelyFinalizes(boolean)metodo gemello , il programma che usa la mia collezione può (aiutare) garantire che la distruzione di un riferimento a questa raccolta distrugge anche i riferimenti al suo contenuto e elimina qualsiasi finestra che possa mantenere in vita la JVM involontariamente. Ho anche considerato di interrompere qualsiasi thread, ma ciò ha aperto una nuova lattina di worm.


4
Chiamare le finalize()tue CompleteObjectistanze come fai nel codice precedente implica che potrebbe essere chiamato due volte, poiché la JVM potrebbe ancora invocare finalize()una volta che questi oggetti sono effettivamente diventati irraggiungibili, che, a causa della logica della finalizzazione, potrebbe essere prima del finalize()metodo della tua raccolta speciale viene chiamato o anche contemporaneamente allo stesso tempo. Dal momento che non vedo alcuna misura per garantire la sicurezza del thread nel tuo metodo, sembra che tu non sia a conoscenza di questo ...
Holger,

0

La risposta accettata indica che è possibile eseguire la chiusura di una risorsa durante la finalizzazione.

Comunque questa risposta mostra che almeno in java8 con il compilatore JIT, si verificano problemi imprevisti in cui a volte viene chiamato il finalizzatore anche prima di terminare la lettura da un flusso gestito dall'oggetto.

Quindi anche in quella situazione non è consigliabile chiamare finalizzare .


Funzionerebbe se l'operazione rendesse il thread sicuro, il che è un must, poiché i finalizzatori possono essere chiamati da thread arbitrari. Poiché la sicurezza del thread correttamente implementata implica che non solo l'operazione di chiusura, ma anche le operazioni che utilizzano la risorsa devono sincronizzarsi sullo stesso oggetto o blocco, ci sarà una relazione prima dell'uso tra l'operazione di chiusura e quella, che impedisce anche troppo presto finalizzazione. Ma, ovviamente, a un prezzo elevato per l'intero utilizzo ...
Holger,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.