Perché dovresti implementare finalize ()?

Ho letto molte domande di rookie Java su finalize () e ho trovato una sorta di sconcerto che nessuno ha davvero reso chiaro che finalize () è un modo inaffidabile per ripulire le risorse. Ho visto qualcuno commentare che lo usano per ripulire Connections, il che è davvero spaventoso in quanto l’unico modo per avvicinarsi alla garanzia che una connessione sia chiusa è implementare try (catch) alla fine.

Non ho studiato a CS, ma ho iniziato a programmare professionalmente in Java per quasi un decennio e non ho mai visto nessuno implementare finalize () in un sistema di produzione di sempre. Questo ancora non significa che non ha i suoi usi, o che le persone con cui ho lavorato lo hanno fatto bene.

Quindi la mia domanda è: quali casi d’uso ci sono per implementare finalize () che non può essere gestito in modo più affidabile attraverso un altro processo o syntax all’interno del linguaggio?

Si prega di fornire scenari specifici o la propria esperienza, semplicemente ripetendo un libro di testo Java, o finalizzare l’uso previsto non è sufficiente, e non è l’intento di questa domanda.

Potresti usarlo come protezione per un object che contiene una risorsa esterna (socket, file, ecc.). Implementare un metodo close() e documentare che deve essere chiamato.

Implementare finalize() per eseguire l’elaborazione close() se si rileva che non è stata eseguita. Magari con qualcosa buttato a stderr per sottolineare che stai pulendo dopo un buggy chiamante.

Fornisce maggiore sicurezza in una situazione eccezionale / inceppata. Non tutti i chiamanti faranno il try {} finally {} giusto ogni volta. Sfortunato, ma vero nella maggior parte degli ambienti.

Sono d’accordo che raramente è necessario. E come sottolineano i commentatori, viene fornito con il sovraccarico di GC. Utilizzalo solo se hai bisogno della sicurezza “cintura e bretelle” in un’app di lunga durata.

Vedo che a partire da Java 9, Object.finalize() è deprecato! Ci indicano java.lang.ref.Cleaner e java.lang.ref.PhantomReference come alternative.

finalize () è un suggerimento per JVM che potrebbe essere bello eseguire il codice in un momento non specificato. Questo è utile quando si vuole che il codice fallisca misteriosamente.

Fare qualcosa di significativo nei finalizzatori (praticamente qualsiasi cosa eccetto il logging) è anche buono in tre situazioni:

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

Se pensate di aver bisogno di finalize (), a volte ciò che volete veramente è un riferimento fantasma (che nell’esempio fornito potrebbe contenere un riferimento a una connessione utilizzata dal suo referente e chiuderlo dopo che il riferimento fantasma è stato accodato). Questo ha anche la proprietà che può misteriosamente non essere mai eseguito, ma almeno non può chiamare metodi su o resuscitare oggetti finalizzati. Quindi è giusto per situazioni in cui non hai assolutamente bisogno di chiudere la connessione in modo pulito, ma ti piacerebbe molto, e i clienti della tua class non possono o non chiameranno da soli (che è abbastanza giusto – che senso ha 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 essere più robusta. Ad esempio, perché hai bisogno di chiudere quella connessione? In ultima analisi, deve basarsi su un qualche tipo di I / O fornito dal sistema (socket, file, qualsiasi cosa), quindi perché non si può fare affidamento sul sistema per chiuderlo quando il livello più basso di risorse viene convertito? Se il server all’altra estremità richiede assolutamente di chiudere la connessione in modo pulito invece di lasciare semplicemente il socket, cosa succederà quando qualcuno inciampa sul cavo di alimentazione della macchina su cui è in esecuzione il codice o la rete che si interrompe si spegne?

Disclaimer: ho lavorato a un’implementazione JVM in passato. Odio i finalizzatori.

Una regola semplice: non utilizzare mai i finalizzatori. Il solo fatto che un object abbia un finalizzatore (indipendentemente dal codice che esegue) è sufficiente a causare un considerevole overhead per la garbage collection.

Da un articolo di Brian Goetz:

Gli oggetti con finalizzatori (quelli che hanno un metodo finalize () non banale) hanno un overhead significativo rispetto agli oggetti senza finalizzatori e dovrebbero essere usati con parsimonia. Gli oggetti finalizzabili sono entrambi più lenti da allocare e 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 JVM di HotSpot) 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. Sono necessari almeno due cicli di garbage collection (nel migliore dei casi) prima che un object finalizzabile possa essere recuperato, e il garbage collector deve fare del lavoro extra per richiamare il finalizzatore. Il risultato è più tempo dedicato all’assegnazione e alla raccolta di oggetti e ad una maggiore pressione sul garbage collector, poiché la memoria utilizzata da oggetti non finalizzabili non recuperabili viene conservata più a lungo. Combinatelo con il fatto che non è garantito che i finalizzatori vengano eseguiti in qualsiasi momento temporale prevedibile, o addirittura del tutto, e potete vedere che ci sono relativamente poche situazioni per le quali la finalizzazione è lo strumento giusto da usare.

L’unica volta che ho usato finalizzare il codice di produzione è stato quello di implementare un controllo che le risorse di un dato object erano state ripulite, e in caso contrario, quindi registrare un messaggio molto vocale. In realtà non ha provato a farlo da solo, ma ha urlato parecchio se non fosse stato eseguito correttamente. Si è rivelato molto utile.

Ho fatto Java professionalmente dal 1998 e non ho mai implementato finalize (). Non una volta.

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) dovrebbe essere usato.

Ho usato finalizzare una volta per capire quali oggetti venivano liberati. Puoi giocare con giochi statici, conteggio dei riferimenti e simili, ma era solo per analisi.

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

Guarda le classi “Reference”. Riferimento debole, ecc.

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

Un’altra cosa a cui prestare attenzione. Ogni volta che vedi qualcosa di simile ovunque nel codice (non solo in finalizzazione, ma è quello che più probabilmente lo vedrai):

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

È un segno che qualcuno non sapeva cosa stavano facendo. “Pulire” come questo non è praticamente mai necessario. Quando la class è GC, ciò viene fatto automaticamente.

Se trovi un codice del genere in un finalize, è garantito che la persona che lo ha scritto è stato confuso.

Se è altrove, potrebbe essere che il codice sia una patch valida per un modello errato (una class rimane in giro per un lungo periodo di tempo e per qualche motivo le cose a cui si faceva riferimento dovevano essere liberate manualmente prima che l’object fosse GC). Generalmente è perché qualcuno ha dimenticato di rimuovere un ascoltatore o qualcosa del genere e non riesce a capire perché il loro object non sia GC, quindi elimina solo le cose a cui si riferisce e alza le spalle e si allontana.

Non dovrebbe mai essere usato per pulire le cose “Più veloce”.

 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:

Questo è finalizzato

MyObject è ancora vivo!

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

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

finalizzare può essere utile per individuare le perdite di risorse. Se la risorsa deve essere chiusa ma non è scritta il fatto che non è stata chiusa in un file di log e chiusa. In questo modo rimuovi la perdita di risorse e ti dai un modo per sapere che è successo in modo da poterlo risolvere.

Ho iniziato a programmare in Java a partire da 1.0 Alpha 3 (1995) e devo ancora eseguire l’override finalize per qualsiasi cosa …

Non dovresti dipendere da finalize () per ripulire le tue risorse. finalize () non verrà eseguito fino a quando la class non verrà raccolta, se ansible. È molto meglio liberare risorse in modo esplicito quando hai finito di usarle.

Per evidenziare un punto nelle risposte di cui sopra: i finalizzatori verranno eseguiti sul thread GC solitario. Ho sentito parlare di una importante demo di Sun in cui gli sviluppatori hanno aggiunto un po ‘di sonno ad alcuni finalizzatori e messo intenzionalmente in ginocchio una demo 3D altrimenti fantasiosa.

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

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

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 questi altri sviluppatori dimenticano di chiamare il metodo cleanup (o close, or destroy o qualunque). Per evitare possibili perdite di risorse, è ansible controllare il metodo finalize per assicurarsi che il metodo sia stato chiamato e, in caso contrario, è ansible chiamarlo manualmente.

Molti driver di database lo fanno nelle loro implementazioni Statement e Connection per fornire un po ‘di sicurezza agli sviluppatori che si dimenticano di chiamare da vicino.

Hmmm, l’ho usato una volta per ripulire gli oggetti che non venivano restituiti a un pool esistente. Sono stati girati molto, quindi era imansible capire quando potevano essere riportati in piscina. Il problema era che introdusse una penalità enorme durante la raccolta dei rifiuti che era molto più grande di qualsiasi risparmio derivante dal mettere in comune gli oggetti. È stato in produzione per circa un mese prima che strappassi l’intera piscina, rendessi tutto dinamico e avessi finito con esso.

Edit: Ok, 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 volta sola.

Non sono un programmatore professionista ma nel mio programma ho un caso che ritengo essere un esempio di buon caso dell’uso di finalize (), ovvero una cache che scrive il suo contenuto su disco prima che venga distrutta. Perché non è necessario che venga eseguito ogni volta in caso di distruzione, ma accelera il mio programma, spero che non sia stato 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); } 

Può essere utile per rimuovere cose che sono state aggiunte a un posto globale / statico (fuori dal bisogno) e devono essere rimossi quando l’object viene rimosso. Per esempio:

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

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

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

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

Fai attenzione a ciò che fai in 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 ogni circostanza in cui abbiamo usato finalize () per invocare i metodi JNI, avremmo ottenuto un danneggiamento dell’heap java molto brutto. La corruzione non era causata dal codice JNI sottostante, tutte le tracce di memoria andavano bene nelle librerie native. Era solo il fatto che stavamo chiamando i metodi JNI dal finalize ().

Questo era con un JDK 1.5 che è ancora molto diffuso.

Non scopriremmo che qualcosa è andato storto fino a molto tempo dopo, ma alla fine il colpevole era sempre il metodo finalize () che utilizzava le chiamate JNI.

iirc: è ansible utilizzare il metodo finalize come mezzo per implementare un meccanismo di pooling per risorse costose, in modo che non ottengano anche GC.

Gli elenchi delle risposte accettate che possono essere chiuse durante la finalizzazione di una risorsa.

Tuttavia questa risposta mostra che almeno in java8 con il compilatore JIT, si incontrano problemi imprevisti in cui a volte il finalizzatore viene chiamato anche prima di aver terminato la lettura da uno stream gestito dal proprio object.

Quindi, anche in quella situazione, chiamare finalize non sarebbe raccomandato.

Personalmente, non uso quasi mai finalize() tranne in una circostanza rara: ho creato una raccolta di tipo generico personalizzata e ho scritto un metodo finalize() che effettua le seguenti operazioni:

 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 creata che consente di specificare che hai implementato metodi Object implementati raramente come #finalize() , #hashCode() e #clone() )

Quindi, usando un metodo #setDestructivelyFinalizes(boolean) sorella, il programma che usa la mia collezione può (aiutare) garantire che la distruzione di un riferimento a questa collezione distrugga anche i riferimenti al suo contenuto e disponga di windows che potrebbero mantenere la JVM vivi inavvertitamente. Ho preso in considerazione anche l'interruzione di qualsiasi thread, ma questo ha aperto una nuova lattina di worm.

Le risorse (File, Socket, Stream, ecc.) Devono essere chiuse una volta terminato il loro utilizzo. In genere hanno close() metodo close() che generalmente chiamiamo la sezione delle dichiarazioni try-catch . A volte finalize() può anche essere usato da pochi sviluppatori ma IMO non è un modo adatto in quanto non vi è alcuna garanzia che la finalizzazione venga chiamata sempre.

In Java 7 abbiamo ottenuto la dichiarazione try-with-resource 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 BufferedReader richiamando il metodo close() . Se vogliamo, possiamo anche implementare Closeable nelle nostre classi e usarlo in modo simile. IMO sembra più pulito e semplice da capire.

Come nota a margine:

Un object che sovrascrive finalize () è trattato in modo particolare dal garbage collector. Di solito, un object viene immediatamente distrutto durante il ciclo di raccolta dopo che l’object non è più in ambito. Tuttavia, gli oggetti finalizzabili vengono invece spostati in una coda, dove i thread di finalizzazione separati esauriscono la coda ed eseguono il metodo finalize () su ciascun object. Una volta che il metodo finalize () termina, l’object sarà finalmente pronto per la garbage collection nel ciclo successivo.

finalizzare deprecato su java-9