Domanda

Ho letto molte delle domande principianti di Java su finalize () e trovo sconcertante che nessuno abbia chiarito che finalize () è 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 ho studiato CS, ma sto programmando in Java da quasi un decennio e non ho mai visto nessuno implementare finalize () in un sistema di produzione in assoluto. 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 esistono per implementare 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.

È stato utile?

Soluzione

Potresti usarlo come backstop per un oggetto che contiene una risorsa esterna (socket, file, ecc.). Implementa un metodo close () e documenta che deve essere chiamato.

Implementa finalize () per eseguire l'elaborazione close () se rilevi che non è stato eseguito. Forse con qualcosa scaricato in stderr per sottolineare che stai pulendo dopo un chiamante buggy.

Fornisce ulteriore sicurezza in una situazione eccezionale / buggy. Non tutti i chiamanti faranno sempre il prova {} finalmente {} corretto. Sfortunato, ma vero nella maggior parte degli ambienti.

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

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

Altri suggerimenti

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 alla fine.

Se pensi di aver bisogno di finalizzare (), a volte quello che vuoi veramente è un riferimento fantasma (che nell'esempio fornito potrebbe contenere un riferimento reale a una connessione utilizzata dal suo referand, 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 senso ha avere un garbage collector se si progettano interfacce che richiedono un'azione specifica da intraprendere prima della raccolta? Questo ci riporta ai tempi di malloc / free.)

Altre volte hai bisogno della risorsa che ritieni di riuscire a essere più solida. 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 per te quando viene assegnato 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 succederà quando qualcuno inciampa 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.

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:

  

Oggetti con finalizzatori (quelli che   avere un metodo finalize () non banale)   hanno un notevole sovraccarico rispetto a   oggetti senza finalizzatori e dovrebbero   essere usato con parsimonia. Finalizeable   gli oggetti sono entrambi più lenti da allocare   e più lento da collezionare. All'assegnazione   ora, la JVM deve registrarne una qualsiasi   oggetti finalizzabili con la spazzatura   collezionista e (almeno nel   Implementazione di HotSpot JVM)   gli oggetti finalizzabili devono seguire a   percorso di allocazione più lento rispetto alla maggior parte degli altri   oggetti. Allo stesso modo, finalizzabile   anche gli oggetti sono più lenti da raccogliere. esso   richiede almeno due garbage collection   cicli (nel migliore dei casi) prima di a   l'oggetto finalizzabile può essere recuperato,   e il garbage collector deve fare   lavoro extra per invocare il finalizzatore.   Il risultato è più tempo speso   allocare e raccogliere oggetti e   più pressione sulla spazzatura   collezionista, perché la memoria utilizzata da   oggetti finalizzabili non raggiungibili è   trattenuto più a lungo. Combina questo con il   fatto che i finalizzatori non lo sono   garantito per funzionare in qualsiasi prevedibile   intervallo di tempo, o addirittura affatto, e puoi   vedi che ce ne sono relativamente pochi   situazioni per le quali è prevista la finalizzazione   lo strumento giusto da usare.

L'unica volta che ho usato finalizzare nel codice di produzione è stato quello di 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.

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

Non sono sicuro di cosa tu possa fare, 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.

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

Guarda " Riferimenti " classi. Riferimento debole, Phantom Reference e amp; 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 nel 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. " Pulizia " come questo praticamente non è mai necessario. Quando la lezione è 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 fa riferimento devono essere liberate manualmente prima che l'oggetto sia 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ù velocemente".

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!");  
    }
}

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

Risultati:

This is finalize

MyObject still alive!

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

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

finalize () può essere utile per rilevare perdite di risorse. Se la risorsa deve essere chiusa ma non è scritta, è necessario non chiuderla 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 correggere.

Sto programmando in Java dalla 1.0 alpha 3 (1995) e devo ancora ignorare la finalizzazione per qualsiasi cosa ...

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.

Per evidenziare un punto nelle risposte sopra: i finalizzatori saranno eseguiti sul thread GC solitario. 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 fantasiosa.

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

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

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 restituiti al pool. 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 fossi finito.

Fai attenzione a ciò che fai in un 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 era con un JDK 1.5 che è ancora ampiamente usato.

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

Quando si scrive codice che verrà utilizzato da altri sviluppatori che richiede una sorta di "pulizia" " metodo 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.

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 su 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 fatto male.

@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 rimuovere elementi che sono stati aggiunti a un luogo globale / statico (non necessario) e devono essere rimossi quando l'oggetto viene rimosso. Ad esempio:

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

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

    @Override
    protected void finalize() throws Throwable {
        super.finalize();

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

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

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 () deprecated on java-9

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

In Java 7 abbiamo try-with-resources che può essere usata 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 invocando 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.

Personalmente, non ho quasi mai usato finalize () tranne in una rara circostanza: ho creato una raccolta personalizzata di tipo generico e ho scritto un metodo personalizzato finalize () che fa quanto 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 creato che ti consente di specificare che hai implementato metodi Object implementati raramente come #finalize () , #hashCode () e #clone())

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

La risposta accettata elenca che può essere eseguita la chiusura di una risorsa durante la finalizzazione.

Tuttavia questa risposta mostra che almeno in java8 con il compilatore JIT, si verificano problemi imprevisti in cui a volte il finalizzatore viene chiamato anche prima che tu finisca di leggere da uno stream gestito dal tuo oggetto.

Quindi, anche in quella situazione, si consiglia di chiamare finalizzare non .

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top