L'impostazione degli oggetti Java su null fa più qualcosa?


112

Stavo sfogliando alcuni vecchi libri e ho trovato una copia di "Practical Java" di Peter Hagger. Nella sezione delle prestazioni, c'è una raccomandazione per impostare i riferimenti agli oggetti nullquando non sono più necessari.

In Java, l'impostazione dei riferimenti agli oggetti per nullmigliorare le prestazioni o l'efficienza della raccolta dei rifiuti? In caso affermativo, in quali casi si tratta di un problema? Classi container? Composizione dell'oggetto? Classi interne anonime?

Lo vedo nel codice abbastanza spesso. Questo consiglio di programmazione è ormai obsoleto o è ancora utile?


2
Crea un profilo. Nei tempi di esecuzione moderni non dovresti vedere alcun aumento significativo delle prestazioni o dell'ingombro della memoria.
Jason Coco

12
@ Jason, il profilo? Ciò presuppone che creerò un profilo di casi sufficientemente ampio per ottenere un set di risultati sufficientemente buono per rispondere a questa domanda. E che non scelgo una serie di casi in cui la VM sia sufficientemente ottimizzata da mascherare i problemi di gc e prestazioni. Ecco perché lo chiedo qui. Per avere un'idea dei casi in cui questo è un problema.
sal

Risposte:


73

Dipende un po 'da quando stavi pensando di annullare il riferimento.

Se hai una catena di oggetti A-> B-> C, una volta che A non è raggiungibile, A, B e C saranno tutti idonei per la raccolta dei rifiuti (assumendo che nient'altro si riferisca a B o C). Non è necessario, e non è mai stato necessario, impostare esplicitamente i riferimenti A-> B o B-> C a null, ad esempio.

A parte questo, il più delle volte il problema non si pone davvero, perché in realtà hai a che fare con oggetti nelle collezioni. In genere dovresti sempre pensare di rimuovere oggetti da elenchi, mappe ecc. Chiamando l'appropriato metodo remove ().

Il caso in cui non vi deve essere utilizzato un consiglio di riferimenti impostato su NULL era specificamente in un lungo raggio in cui un oggetto intensivo della memoria ha cessato di essere parzialmente usato attraverso il campo di applicazione . Per esempio:

{
  BigObject obj = ...
  doSomethingWith(obj);
  obj = null;             <-- explicitly set to null
  doSomethingElse();
}

La logica qui era che, poiché obj è ancora nell'ambito, quindi senza l'annullamento esplicito del riferimento, non diventa garbage collectable fino al completamento del metodo doSomethingElse () . E questo è il consiglio che probabilmente non vale più per le moderne JVM : si scopre che il compilatore JIT può capire fino a che punto un dato riferimento a un oggetto locale non viene più utilizzato.


2
Le variabili locali possono essere ottimizzate dalla macchina. Le variabili di classe non possono. Ho visto thread di lavoro (su un pool di thread) non rilasciare i loro oggetti di stato dopo aver gestito la richiesta e quindi bloccare quegli oggetti in memoria fino a quando al thread di lavoro è stata assegnata una nuova attività (che ha immediatamente sovrascritto i vecchi oggetti di stato con quelli nuovi). Con grandi oggetti di stato e centinaia di thread di lavoro (si pensi: un grande server http), questa può essere una quantità enorme di memoria trattenuta e copiata ma non verrà mai più utilizzata.
Brian White

1
Probabilmente dovrei solo aggiungere: il consiglio che do sopra è il consiglio da seguire in generale, assumendo librerie ben educate, una VM ben educata ecc. Se scopri di avere, diciamo, una libreria di pool di thread che si blocca sugli oggetti dopo un lavoro è finito, beh ... questo è un bug in detta libreria di pool di thread che potrebbe forse essere aggirato annullando un riferimento a un oggetto, ma allo stesso modo potrebbe essere aggirato usando una libreria di pool di thread meno difettosa. Non sono sicuro che un tale bug cambi i principi generali di progettazione.
Neil Coffey

25

No, non è un consiglio obsoleto. I riferimenti penzolanti sono ancora un problema, specialmente se, ad esempio, stai implementando un contenitore di array espandibile ( ArrayListo simile) utilizzando un array pre-allocato. Gli elementi oltre la dimensione "logica" dell'elenco dovrebbero essere annullati, altrimenti non verranno liberati.

Vedere Effective Java 2nd ed, Item 6: Eliminate Obsolete Object References.


Si tratta di un problema di lingua o di implementazione della VM?
Pablo Santa Cruz

4
È una questione "semantica". Fondamentalmente, poiché hai preallocato un array, la VM lo vede. Non sa nulla della dimensione "logica" del tuo contenitore. Supponiamo di avere un ArrayList di dimensione 10, supportato da un array di 16 elementi. La VM non può sapere che gli elementi 10..15 non sono effettivamente utilizzati; se questi slot hanno dei contenuti, non verranno liberati.
Chris Jester-Young

che dire al di fuori delle classi container? Nella composizione di oggetti in cui l'oggetto interno non è de-allocato dall'oggetto esterno è.
sal

7
@sal La regola generale è che se non puoi fare riferimento ad esso, viene raccolto dalla spazzatura. Quindi, se l'oggetto esterno contiene un riferimento a un altro oggetto, supponendo che l'oggetto interno non abbia altri riferimenti, l'impostazione del solo e unico riferimento dell'oggetto esterno su null farà sì che l'intero oggetto venga sottoposto a garbage collection, inclusi i suoi riferimenti orfani.
ubermensch

2
Nello stesso punto 6 che hai menzionato: l' annullamento dei riferimenti agli oggetti dovrebbe essere l'eccezione piuttosto che la norma.
ACV

10

Campi istanza, elementi array

Se è presente un riferimento a un oggetto, non può essere sottoposto a Garbage Collection. Soprattutto se quell'oggetto (e l'intero grafico dietro di esso) è grande, c'è solo un riferimento che interrompe la raccolta dei rifiuti e quel riferimento non è più necessario, questa è una situazione sfortunata.

I casi patologici sono l'oggetto che conserva un'istanza non necessaria per l'intero albero XML DOM utilizzato per configurarlo, l'MBean che non è stato annullato o il singolo riferimento a un oggetto da un'applicazione Web non distribuita che impedisce lo scaricamento di un intero programma di caricamento classi .

Quindi, a meno che tu non sia sicuro che l'oggetto che contiene il riferimento stesso verrà comunque raccolto dalla spazzatura (o anche allora), dovresti annullare tutto ciò che non ti serve più.

Variabili con ambito:

Se stai considerando di impostare una variabile locale su null prima della fine del suo ambito, in modo che possa essere recuperata dal garbage collector e contrassegnarla come "inutilizzabile da ora in poi", dovresti invece considerare di metterla in uno scope più limitato .

{
  BigObject obj = ...
  doSomethingWith(obj);
  obj = null;          //   <-- explicitly set to null
  doSomethingElse();
}

diventa

{
  {  
     BigObject obj = ...
     doSomethingWith(obj);
  }    //         <-- obj goes out of scope
  doSomethingElse();
}

Anche gli ambiti lunghi e piatti sono generalmente dannosi per la leggibilità del codice. Anche l'introduzione di metodi privati ​​per rompere le cose solo per quello scopo non è inaudita.


Se ti riferisci a un riferimento forte, sì, questo è vero, ma non per tutti i riferimenti. I riferimenti deboli in java possono essere raccolti in modo inutile.
James Drinkard,

5

In ambienti con limitazioni di memoria (ad esempio telefoni cellulari) questo può essere utile. Impostando null, objetc non ha bisogno di aspettare che la variabile esca dall'ambito per essere gc.

Per la programmazione quotidiana, tuttavia, questa non dovrebbe essere la regola, tranne in casi speciali come quello citato da Chris Jester-Young.


1

In primo luogo, non significa nulla su cui stai impostando un oggetto null. Lo spiego di seguito:

List list1 = new ArrayList();
List list2 = list1;

Nel segmento di codice sopra stiamo creando il nome list1della variabile di riferimento dell'oggetto ArrayListdell'oggetto che è archiviato nella memoria. Quindi list1si riferisce a quell'oggetto e non è altro che una variabile. E nella seconda riga di codice stiamo copiando il riferimento di list1a list2. Quindi ora tornando alla tua domanda se lo faccio:

list1 = null;

ciò significa che list1non si fa più riferimento ad alcun oggetto archiviato nella memoria, quindi non list2avrà più nulla a cui fare riferimento. Quindi se controlli la dimensione di list2:

list2.size(); //it gives you 0

Quindi ecco che arriva il concetto di garbage collector che dice «non devi preoccuparti di liberare la memoria che è trattenuta dall'oggetto, lo farò quando scoprirò che non sarà più usato nel programma e JVM mi gestirà».

Spero chiarisca il concetto.


0

Uno dei motivi per farlo è eliminare i riferimenti agli oggetti obsoleti. Puoi leggere il testo qui.

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.