L'assegnazione di oggetti a null in Java influisce sulla garbage collection?


85

L'assegnazione di un riferimento a un oggetto inutilizzato nullin Java migliora il processo di raccolta dei rifiuti in modo misurabile?

La mia esperienza con Java (e C #) mi ha insegnato che spesso è controintuitivo provare a superare in astuzia la macchina virtuale o il compilatore JIT, ma ho visto colleghi usare questo metodo e sono curioso se questa è una buona pratica da scegliere o una di quelle superstizioni della programmazione voodoo?

Risposte:


66

Di solito no.

Ma come tutte le cose: dipende. Il GC in Java in questi giorni è MOLTO buono e tutto dovrebbe essere ripulito molto poco dopo che non è più raggiungibile. Questo è subito dopo aver lasciato un metodo per le variabili locali e quando un'istanza di classe non è più referenziata per i campi.

Hai solo bisogno di null esplicitamente se sai che rimarrebbe referenziato altrimenti. Ad esempio un array che viene mantenuto in giro. Potresti voler annullare i singoli elementi dell'array quando non sono più necessari.

Ad esempio, questo codice da ArrayList:

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
         System.arraycopy(elementData, index+1, elementData, index,
             numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
}

Inoltre, l'annullamento esplicito di un oggetto non causerà la raccolta di un oggetto prima che se uscisse dall'ambito in modo naturale finché non rimangono riferimenti.

Tutti e due:

void foo() {
   Object o = new Object();
   /// do stuff with o
}

e:

void foo() {
   Object o = new Object();
   /// do stuff with o
   o = null;
}

Sono funzionalmente equivalenti.


7
potresti anche voler annullare i riferimenti all'interno di metodi che possono utilizzare una grande quantità di memoria o richiedere molto tempo per l'esecuzione, o nel codice in un'applicazione Swing che può essere eseguita a lungo. In metodi brevi o oggetti di breve durata, non vale la pena il codice confuso in più.
John Gardner,

1
Quindi è giusto, quando annulliamo un oggetto, il Garbage Collector lo raccoglie immediatamente?
Muhammad Babar

1
No. Il garbage collector viene eseguito periodicamente per vedere se c'erano oggetti a cui non puntano variabili di riferimento. Quando li imposti su null e quindi chiami System.gc(), verrà rimosso (a condizione che non ci siano altri riferimenti che puntano a quell'oggetto).
JavaTechnical

2
@JavaTechnical Come so, non è garantito che chiamando System.gc () inizi la garbage collection.
Jack

12

Nella mia esperienza, il più delle volte, le persone annullano i riferimenti per paranoia, non per necessità. Ecco una breve guida:

  1. Se l'oggetto A fa riferimento all'oggetto B e non è più necessario questo riferimento e l' oggetto A non è idoneo per la garbage collection, è necessario annullare esplicitamente il campo. Non è necessario annullare un campo se l'oggetto che lo racchiude viene comunque raccolto dalla spazzatura. L'annullamento dei campi in un metodo dispose () è quasi sempre inutile.

  2. Non è necessario annullare i riferimenti agli oggetti creati in un metodo. Verranno cancellati automaticamente una volta terminato il metodo. L'eccezione a questa regola è se stai eseguendo un metodo molto lungo o un ciclo massiccio e devi assicurarti che alcuni riferimenti vengano cancellati prima della fine del metodo. Ancora una volta, questi casi sono estremamente rari.

Direi che la stragrande maggioranza delle volte non sarà necessario annullare i riferimenti. Cercare di superare in astuzia il garbage collector è inutile. Ti ritroverai con un codice inefficiente e illeggibile.


11

Un buon articolo è l' orrore della programmazione di oggi .

Il modo in cui il lavoro di GC è cercare oggetti che non hanno alcun puntatore ad essi, l'area della loro ricerca è heap / stack e qualsiasi altro spazio che hanno. Quindi, se imposti una variabile su null, l'oggetto effettivo ora non viene puntato da nessuno e quindi potrebbe essere GC.

Ma poiché il GC potrebbe non funzionare in quell'istante esatto, potresti non comprarti nulla. Ma se il tuo metodo è abbastanza lungo (in termini di tempo di esecuzione) potrebbe valerne la pena poiché aumenterai le tue possibilità di GC raccogliere quell'oggetto.

Il problema può anche essere complicato con le ottimizzazioni del codice, se non si utilizza mai la variabile dopo averla impostata su null, sarebbe un'ottimizzazione sicura rimuovere la riga che imposta il valore su null (un'istruzione in meno da eseguire). Quindi potresti non ottenere alcun miglioramento.

Quindi, in sintesi, sì, può aiutare, ma non sarà deterministico .



Buon punto sull'ottimizzazione sicura che rimuove la linea impostando il riferimento a null.
asgs

8

Almeno in java, non è affatto programmazione voodoo. Quando crei un oggetto in java usando qualcosa come

Foo bar = new Foo();

fai due cose: in primo luogo, crei un riferimento a un oggetto e, in secondo luogo, crei l'oggetto Foo stesso. Finché esiste quel riferimento o un altro, l'oggetto specifico non può essere gc'd. tuttavia, quando assegni null a quel riferimento ...

bar = null ;

e supponendo che nient'altro abbia un riferimento all'oggetto, è liberato e disponibile per gc la prossima volta che passa il garbage collector.


12
Ma uscire dall'ambito farà la stessa cosa senza la riga di codice aggiuntiva. Se è locale per un metodo, non è necessario. Una volta abbandonato il metodo, l'oggetto sarà idoneo per GC.
duffymo,

2
Buona. ora, per un pop quiz, costruisci un esempio di codice per il quale NON è sufficiente. Suggerimento: esistono.
Charlie Martin

7

Dipende.

In generale, più breve mantieni i riferimenti ai tuoi oggetti, più velocemente verranno raccolti.

Se il tuo metodo impiega 2 secondi per essere eseguito e non hai più bisogno di un oggetto dopo un secondo di esecuzione del metodo, ha senso cancellare qualsiasi riferimento ad esso. Se GC vede che dopo un secondo, il tuo oggetto è ancora referenziato, la prossima volta potrebbe controllarlo tra un minuto circa.

Ad ogni modo, impostare tutti i riferimenti a null per impostazione predefinita è per me un'ottimizzazione prematura e nessuno dovrebbe farlo a meno che in rari casi specifici in cui diminuisca in modo misurabile il consumo di memoria.


6

L'impostazione esplicita di un riferimento a null invece di lasciare semplicemente che la variabile esca dall'ambito, non aiuta il garbage collector, a meno che l'oggetto contenuto non sia molto grande, dove impostarlo su null non appena hai finito è una buona idea.

Generalmente impostare i riferimenti a null, significa al LETTORE del codice con cui questo oggetto è completamente finito e non dovrebbe più preoccuparsi.

Un effetto simile può essere ottenuto introducendo un ambito più ristretto inserendo un ulteriore set di parentesi graffe

{
  int l;
  {  // <- here
    String bigThing = ....;
    l = bigThing.length();
  }  // <- and here
}

questo consente al bigThing di essere sottoposto a garbage collection subito dopo aver lasciato le parentesi graffe annidate.


Buona alternativa. Ciò evita di impostare esplicitamente la variabile su null e il lint che avvisa alcuni IDE di non utilizzare quell'assegnazione null.
jk7

5
public class JavaMemory {
    private final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public void f() {
        {
            byte[] data = new byte[dataSize];
            //data = null;
        }

        byte[] data2 = new byte[dataSize];
    }

    public static void main(String[] args) {

        JavaMemory jmp = new JavaMemory();
        jmp.f();

    }

}

Sopra i lanci del programma OutOfMemoryError. Se rimuovi data = null;il commento , OutOfMemoryErrorviene risolto. È sempre buona norma impostare la variabile inutilizzata su null


Cioè, imo, una solida incursione nel mondo degli argomenti dell'uomo di paglia. Solo perché PUOI creare una situazione in cui l'impostazione della variabile su null risolverà il problema in quel caso particolare (e non sono convinto che tu l'abbia fatto) non implica che impostarla su null sia una buona idea in ogni caso. Aggiungendo il codice per impostare ogni variabile che non stai più utilizzando su null, anche quando usciranno dall'ambito poco dopo, si aggiunge semplicemente al codice gonfio e rende il codice meno gestibile.
RHSeeger

Perché l'array byte [] non viene raccolto in modo indesiderato nel momento in cui i dati della variabile non rientrano nell'ambito?
Grav

2
@ Grav: uscire dall'ambito non garantisce che il Garbage Collector raccoglierà gli oggetti locali. Tuttavia, impostarlo su null impedisce l'eccezione OutOfMemory, perché si è certi che il GC verrà eseguito prima di ricorrere alla generazione di un'eccezione OutOfMemory e se l'oggetto è stato impostato su null sarà recuperabile.
Stefanos T.


4

Una volta stavo lavorando a un'applicazione di videoconferenza e ho notato un'enorme differenza enorme nelle prestazioni quando ho preso il tempo per annullare i riferimenti non appena non avevo più bisogno dell'oggetto. Era il 2003-2004 e da allora posso solo immaginare che il GC sia diventato ancora più intelligente. Nel mio caso avevo centinaia di oggetti che entravano e uscivano dall'ambito ogni secondo, quindi ho notato il GC quando si attivava periodicamente. Tuttavia, dopo aver puntato a oggetti nulli, il GC ha smesso di sospendere la mia applicazione.

Quindi dipende da cosa stai facendo ...


2

Sì.

Da "The Pragmatic Programmer" p.292:

Impostando un riferimento a NULL riduci il numero di puntatori all'oggetto di uno ... (il che consentirà al garbage collector di rimuoverlo)


Immagino che questo sia applicabile solo quando l'algoritmo di conteggio dei riferimenti. è in uso o anche in altro modo?
Nrj

3
Questo consiglio è obsoleto per qualsiasi moderno garbage collector; e il conteggio dei riferimenti GC presenta problemi significativi, come i riferimenti circolari.
Lawrence Dol

Sarebbe anche vero in un mark and sweep GC; probabilmente in tutti gli altri. Il fatto che tu abbia un riferimento in meno non significa che sia contato. La risposta di tweakt lo spiega. È meglio ridurre l'ambito piuttosto che impostare su NULL.

1

Presumo che l'OP si riferisca a cose come questa:

private void Blah()
{
    MyObj a;
    MyObj b;

    try {
        a = new MyObj();
        b = new MyObj;

        // do real work
    } finally {
        a = null;
        b = null;
    }
}

In questo caso, la VM non li contrassegnerebbe comunque per GC non appena abbandonano l'ambito?

Oppure, da un'altra prospettiva, l'impostazione esplicita degli elementi su null causerebbe loro la GC prima che lo farebbero se uscissero dall'ambito? In tal caso, la VM potrebbe impiegare del tempo a eseguire il GC sull'oggetto quando la memoria non è comunque necessaria, il che in realtà causerebbe prestazioni peggiori per l'utilizzo della CPU perché sarebbe GC più presto.


L'ora in cui viene eseguito il GC non è deterministica. Non credo affatto che l'impostazione di un oggetto nullinfluenzi il comportamento del GC.
harto

0

" Dipende "

Non conosco Java ma in .net (C #, VB.net ...) di solito non è necessario assegnare un null quando non è più necessario un oggetto.

Tuttavia si noti che "di solito non è richiesto".

Analizzando il tuo codice il compilatore .net fa una buona valutazione del tempo di vita della variabile ... per dire con precisione quando l'oggetto non viene più utilizzato. Quindi, se scrivi obj = null, potrebbe effettivamente sembrare che obj sia ancora in uso ... in questo caso è controproducente assegnare un null.

Ci sono alcuni casi in cui potrebbe effettivamente essere utile assegnare un valore nullo. Un esempio è che hai un codice enorme che viene eseguito per molto tempo o un metodo che viene eseguito in un thread diverso o in un ciclo. In questi casi potrebbe essere utile assegnare un valore nullo in modo che sia facile per il GC sapere che non viene più utilizzato.

Non esiste una regola rigida e veloce per questo. Andando dal posto sopra null-assigns nel tuo codice ed esegui un profiler per vedere se aiuta in qualche modo. Molto probabilmente potresti non vedere un vantaggio.

Se si tratta di codice .net che stai cercando di ottimizzare, la mia esperienza è stata che prendersi cura dei metodi Dispose e Finalize è in realtà più vantaggioso che preoccuparsi dei null.

Alcuni riferimenti sull'argomento:

http://blogs.msdn.com/csharpfaq/archive/2004/03/26/97229.aspx

http://weblogs.asp.net/pwilson/archive/2004/02/20/77422.aspx


0

Anche se annullare il riferimento fosse leggermente più efficiente, varrebbe la pena di dover infarcire il codice con queste brutte annullamenti? Sarebbero solo disordine e oscurerebbero il codice di intenti che li contiene.

È una base di codice rara che non ha un candidato migliore per l'ottimizzazione che cercare di superare in astuzia il Garbage Collector (ancora più rari sono gli sviluppatori che riescono a superarlo in astuzia). I tuoi sforzi saranno molto probabilmente spesi meglio altrove, abbandonando quel crufty parser Xml o trovando qualche opportunità per memorizzare nella cache il calcolo. Queste ottimizzazioni saranno più facili da quantificare e non richiedono di sporcare la base del codice con il rumore.


0

Nella futura esecuzione del programma, i valori di alcuni membri dati verranno utilizzati per elaborare un output visibile all'esterno del programma. Altri potrebbero o non potrebbero essere utilizzati, a seconda degli input futuri (e impossibili da prevedere) per il programma. Potrebbe essere garantito che altri membri di dati non verranno utilizzati. Tutte le risorse, inclusa la memoria, assegnate a quei dati inutilizzati vengono sprecate. Il compito del Garbage Collector (GC) è eliminare quella memoria sprecata. Sarebbe disastroso per il GC eliminare qualcosa che era necessario, quindi l'algoritmo utilizzato potrebbe essere conservativo, mantenendo più dello stretto minimo. Potrebbe utilizzare ottimizzazioni euristiche per migliorare la velocità, al costo di mantenere alcuni elementi che non sono effettivamente necessari. Esistono molti potenziali algoritmi che il GC potrebbe utilizzare. Pertanto è possibile che le modifiche apportate al programma, e che non influiscono sulla correttezza del programma, potrebbero tuttavia influire sul funzionamento del GC, rendendolo più veloce per eseguire lo stesso lavoro o per identificare prima gli elementi inutilizzati. Quindi questo tipo di modifica, impostando un riferimento a un oggetto insolito sunull, in teoria non è sempre voodoo.

È voodoo? Secondo quanto riferito, ci sono parti del codice della libreria Java che lo fanno. Gli autori di quel codice sono molto migliori dei programmatori medi e conoscono, o collaborano con, programmatori che conoscono i dettagli delle implementazioni del garbage collector. In modo che suggerisce ci sia a volte un vantaggio.


0

Come hai detto, ci sono ottimizzazioni, cioè JVM conosce il luogo in cui la variabile è stata usata l'ultima volta e l'oggetto a cui fa riferimento può essere GCed subito dopo quest'ultimo punto (ancora in esecuzione nell'ambito corrente). Quindi annullare i riferimenti nella maggior parte dei casi non aiuta GC.

Ma può essere utile per evitare problemi di "nepotismo" (o "spazzatura fluttuante") ( leggi di più qui o guarda il video ). Il problema esiste perché l'heap è suddiviso in generazioni Old e Young e vengono applicati diversi meccanismi GC: Minor GC (che è veloce e capita spesso per pulire young gen) e Major Gc (che causa una pausa più lunga per pulire Old gen). Il "nepotismo" non consente la raccolta di rifiuti in Young gen se è referenziato da spazzatura già assegnata a Old gen.

Questo è "patologico" perché QUALSIASI nodo promosso comporterà la promozione di TUTTI i nodi successivi fino a quando un GC non risolverà il problema.

Per evitare il nepotismo è una buona idea annullare i riferimenti da un oggetto che dovrebbe essere rimosso. Puoi vedere questa tecnica applicata nelle classi JDK: LinkedList e LinkedHashMap

private E unlinkFirst(Node<E> f) {
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    // ...
}
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.