Domanda

Dopo aver letto questa domanda, mi sono ricordato di quando mi è stato insegnato Java e mi è stato detto di non chiamare mai finalize() o eseguire il garbage collector perché "è una grande scatola nera di cui non devi mai preoccuparti".Qualcuno può riassumere il ragionamento in poche frasi?Sono sicuro che potrei leggere un rapporto tecnico di Sun su questo argomento, ma penso che una risposta carina, breve e semplice soddisferebbe la mia curiosità.

È stato utile?

Soluzione

La risposta breve:La garbage collection Java è uno strumento molto preciso.System.gc() è una mazza.

L'heap di Java è diviso in diverse generazioni, ciascuna delle quali viene raccolta utilizzando una strategia diversa.Se colleghi un profiler a un'app sana, vedrai che molto raramente deve eseguire i tipi di raccolte più costosi perché la maggior parte degli oggetti vengono catturati dal collezionista che copia più velocemente nella giovane generazione.

La chiamata diretta a System.gc(), anche se tecnicamente non è garantito che faccia nulla, in pratica attiverà una raccolta heap completa e costosa, da fermare il mondo.Questo è quasi sempre la cosa sbagliata da fare.Pensi di risparmiare risorse, ma in realtà le stai sprecando senza una buona ragione, costringendo Java a ricontrollare tutti i tuoi oggetti live "per ogni evenienza".

Se riscontri problemi con le pause GC durante i momenti critici, è meglio configurare la JVM per utilizzare il raccoglitore mark/sweep simultaneo, che è stato progettato specificamente per ridurre al minimo il tempo trascorso in pausa, piuttosto che provare a prendere a martellate il problema e semplicemente rompendolo ulteriormente.

Il documento Sun a cui stavi pensando è qui: Ottimizzazione della Garbage Collection della macchina virtuale Java SE 6 HotSpot™

(Un'altra cosa che potresti non sapere:l'implementazione di un metodo finalize() sul tuo oggetto rallenta la raccolta dei rifiuti.In primo luogo, ci vorrà due GC corre a raccogliere l'oggetto:uno per eseguire finalize() e il successivo per garantire che l'oggetto non sia stato resuscitato durante la finalizzazione.In secondo luogo, gli oggetti con metodi finalize() devono essere trattati come casi speciali dal GC perché devono essere raccolti individualmente e non possono essere semplicemente gettati via in blocco.)

Altri suggerimenti

Non preoccuparti dei finalizzatori.

Passa alla garbage collection incrementale.

Se vuoi aiutare il garbage collector, annulla i riferimenti agli oggetti che non ti servono più.Meno percorso da seguire = più esplicitamente spazzatura.

Non dimenticare che le istanze della classe interna (non statiche) mantengono i riferimenti all'istanza della classe genitore.Quindi un thread di classe interna mantiene molto più bagaglio di quanto potresti aspettarti.

In modo molto correlato, se stai utilizzando la serializzazione e hai serializzato oggetti temporanei, dovrai svuotare le cache di serializzazione, chiamando ObjectOutputStream.reset() altrimenti il ​​tuo processo perderà memoria e alla fine morirà.Lo svantaggio è che gli oggetti non transitori verranno riserializzati.La serializzazione di oggetti risultato temporanei può essere un po' più complicata di quanto si possa pensare!

Prendi in considerazione l'utilizzo di riferimenti soft.Se non sai cosa sono i soft reference, leggi il javadoc per java.lang.ref.SoftReference

Evita i riferimenti fantasma e i riferimenti deboli a meno che non diventi davvero eccitabile.

Infine, se proprio non riesci a tollerare il GC, usa Realtime Java.

No, non sto scherzando.

L'implementazione di riferimento può essere scaricata gratuitamente e il libro di Peter Dibbles di SUN è davvero un'ottima lettura.

Per quanto riguarda i finalizzatori:

  1. Sono praticamente inutili.Non è garantito che vengano chiamati in modo tempestivo o addirittura che non vengano affatto eseguiti (se il GC non viene mai eseguito, non lo faranno nemmeno i finalizzatori).Ciò significa che generalmente non dovresti fare affidamento su di loro.
  2. Non è garantito che i finalizzatori siano idempotenti.Il garbage collector fa molta attenzione a garantire che non chiamerà mai finalize() più di una volta sullo stesso oggetto.Con oggetti ben scritti, non avrà importanza, ma con oggetti scritti male, chiamare finalize più volte può causare problemi (ad es.doppio rilascio di una risorsa nativa...incidente).
  3. Ogni oggetto che ha a finalize() il metodo dovrebbe anche fornire a close() (o metodo simile).Questa è la funzione che dovresti chiamare.per esempio., FileInputStream.close().Non c'è motivo di chiamare finalize() quando hai un metodo più appropriato quello È destinato a essere chiamato da te.

Supponendo che i finalizzatori siano simili al loro omonimo .NET, è necessario chiamarli solo quando si dispone di risorse come handle di file che possono perdere.Nella maggior parte dei casi i tuoi oggetti non hanno questi riferimenti, quindi non è necessario chiamarli.

È brutto cercare di raccogliere la spazzatura perché non è proprio la tua spazzatura.Hai detto alla VM di allocare memoria quando hai creato oggetti e il garbage collector nasconde informazioni su tali oggetti.Internamente il GC sta eseguendo ottimizzazioni sulle allocazioni di memoria che effettua.Quando provi manualmente a raccogliere la spazzatura non hai conoscenza di ciò che il GC vuole trattenere e di cui sbarazzarsi, stai solo forzando la mano.Di conseguenza si confondono i calcoli interni.

Se sapessi di più su ciò che il GC teneva internamente, potresti essere in grado di prendere decisioni più informate, ma in questo caso ti saresti perso i vantaggi di GC.

Il vero problema con la chiusura degli handle del sistema operativo in finalize è che la finalizzazione viene eseguita in un ordine non garantito.Ma se hai delle maniglie per le cose che bloccano (pensa ad es.socket) potenzialmente il tuo codice può entrare in una situazione di stallo (per niente banale).

Quindi sono favorevole alla chiusura esplicita delle maniglie in modo ordinato e prevedibile.Fondamentalmente il codice per gestire le risorse dovrebbe seguire lo schema:

SomeStream s = null;
...
try{
   s = openStream();
   ....
   s.io();
   ...
} finally {
   if (s != null) {
       s.close();
       s = null;
   }
}

Diventa ancora più complicato se scrivi le tue classi che funzionano tramite JNI e aprono handle.È necessario assicurarsi che le maniglie siano chiuse (rilasciate) e che ciò accada solo una volta.L'handle del sistema operativo spesso trascurato in Desktop J2SE è Graphics[2D].Anche BufferedImage.getGrpahics() può potenzialmente restituirti l'handle che punta a un driver video (che in realtà contiene la risorsa sulla GPU).Se non lo rilasci tu stesso e lasci che sia il garbage collector a fare il lavoro, potresti trovare strane OutOfMemory e situazioni simili quando hai esaurito le bitmap mappate sulla scheda video ma hai ancora molta memoria.Nella mia esperienza ciò accade piuttosto frequentemente in cicli ristretti lavorando con oggetti grafici (estrazione di miniature, ridimensionamento, nitidezza, come si dice).

Fondamentalmente GC non si assume la responsabilità dei programmatori sulla corretta gestione delle risorse.Si occupa solo della memoria e nient'altro.La chiamata Stream.finalize a close() IMHO sarebbe meglio implementata lanciando un'eccezione new RuntimeError ("garbagecollection del flusso che è ancora aperto").Risparmierà ore e giorni di debug e pulizia del codice dopo che i dilettanti sciatti hanno lasciato perdere i fini.

Buona programmazione.

Pace.

Il GC esegue molte ottimizzazioni su quando finalizzare correttamente le cose.

Pertanto, a meno che tu non abbia familiarità con il funzionamento effettivo del GC e con il modo in cui tagga le generazioni, chiamare manualmente finalize o avviare GC'ing probabilmente danneggerà le prestazioni piuttosto che aiutare.

Evita i finalizzatori.Non vi è alcuna garanzia che verranno chiamati tempestivamente.Potrebbe volerci molto tempo prima che il sistema di gestione della memoria (ovvero il garbage collector) decida di raccogliere un oggetto con un finalizzatore.

Molte persone utilizzano i finalizzatori per eseguire operazioni come chiudere le connessioni socket o eliminare file temporanei.In questo modo rendi il comportamento della tua applicazione imprevedibile e legato a quando la JVM eseguirà il GC del tuo oggetto.Ciò può portare a scenari di "memoria esaurita", non a causa dell'esaurimento dell'heap Java, ma piuttosto a causa dell'esaurimento degli handle del sistema per una particolare risorsa.

Un'altra cosa da tenere a mente è che l'introduzione delle chiamate a System.gc() o simili martelli può mostrare buoni risultati nel tuo ambiente, ma non si tradurranno necessariamente in altri sistemi.Non tutti utilizzano la stessa JVM, ce ne sono molti, SUN, IBM J9, BEA JRockit, Harmony, OpenJDK, ecc...Tutte queste JVM sono conformi al JCK (quelli che sono stati ufficialmente testati), ma hanno molta libertà quando si tratta di rendere le cose veloci.GC è una di quelle aree in cui tutti investono molto.L'uso di un martello spesso distruggerà questo sforzo.

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