2009-03-09 14 views
36

Sto prendendo in carico alcune applicazioni da uno sviluppatore precedente. Quando eseguo le applicazioni tramite Eclipse, vedo l'utilizzo della memoria e le dimensioni dell'heap aumentano molto. Dopo ulteriori indagini, vedo che stavano creando un oggetto sovrapposto in un ciclo e altre cose.Quali sono le migliori pratiche di gestione della memoria Java?

Ho iniziato a passare e fare un po 'di pulizia. Ma più ho attraversato, più domande ho avuto "questo farà davvero qualcosa?"

Ad esempio, invece di dichiarare una variabile al di fuori del ciclo menzionato sopra e semplicemente impostando il suo valore nel ciclo ... hanno creato l'oggetto nel ciclo. Quello che voglio dire è:

for(int i=0; i < arrayOfStuff.size(); i++) { 
    String something = (String) arrayOfStuff.get(i); 
    ... 
} 

contro

String something = null; 
for(int i=0; i < arrayOfStuff.size(); i++) { 
    something = (String) arrayOfStuff.get(i); 
} 

Am I corretto dire che il ciclo di fondo è meglio? Forse mi sbaglio.

Inoltre, dopo il secondo ciclo precedente, ho impostato "qualcosa" su null? Potresti cancellare un po 'di memoria?

In entrambi i casi, quali sono le migliori pratiche di gestione della memoria che potrei seguire per mantenere basso l'utilizzo della memoria nelle mie applicazioni?

Aggiornamento:

Apprezzo tutti quanti commenti finora. Tuttavia, non stavo davvero chiedendo i loop precedenti (anche se con il tuo consiglio sono tornato al primo ciclo). Sto cercando di ottenere alcune best practice che posso tenere d'occhio. Qualcosa sulla falsariga di "quando hai finito di usare una collezione, cancellala". Ho solo bisogno di assicurarmi che la maggior parte della memoria non venga utilizzata da queste applicazioni.

+0

Modifica la tua: come tre persone hanno detto finora, profila! –

+0

Sto cercando alcune pratiche specifiche da utilizzare, quindi non devo avere qualcuno che lo descriva. Preferirei svilupparlo in modo appropriato prima di codificarlo, spero che funzioni, profilalo e faccia delle riparazioni. – Ascalonian

+2

La "pratica specifica" è scrivere codice ben strutturato senza fare un'ottimizzazione prematura, e quindi profilarlo per scoprire cosa deve essere ottimizzato. –

risposta

36

Non cercare di superare in astuzia la VM. Il primo ciclo è la best practice consigliata, sia per le prestazioni che per la manutenibilità. L'impostazione del riferimento su null dopo il ciclo non garantisce il rilascio immediato della memoria. Il GC farà il suo lavoro al meglio quando usi l'ambito minimo possibile.

I libri che trattano queste cose in dettaglio (dal punto di vista dell'utente) sono Effective Java 2 e Implementation Patterns.

Se si desidera saperne di più sulle prestazioni e gli interni della VM, è necessario consultare conversazioni o leggere libri da Brian Goetz.

+0

" usa l'ambito minimo possibile "è considerato una buona pratica in generale? – KillBill

+0

Penso che in generale si. Ma ovviamente dovrebbero esserci delle eccezioni a questa generalizzazione. – cherouvim

9

Questi due circuiti sono equivalenti ad eccezione dello scopo di something; vedi this question per dettagli.

Buone pratiche generali? Umm, vediamo: non immagazzinare grandi quantità di dati in variabili statiche a meno che tu non abbia una buona ragione. Rimuovi oggetti di grandi dimensioni dalle collezioni quando hai finito con loro. E oh sì, "Misura, non indovinare". Utilizzare un profiler per vedere dove viene allocata la memoria.

+3

+1 esclusivamente per "Misura, non indovinare". –

5

I due loop useranno fondamentalmente la stessa quantità di memoria, qualsiasi differenza sarebbe trascurabile. "String something" crea solo un riferimento a un oggetto, non un nuovo oggetto in sé e quindi qualsiasi memoria aggiuntiva utilizzata è piccola. Inoltre, il compilatore/combinato con JVM probabilmente ottimizzerà comunque il codice generato.

Per le pratiche di gestione della memoria, è consigliabile provare a profilare meglio la memoria per capire dove sono effettivamente i colli di bottiglia.Guarda in particolare i riferimenti statici che puntano a una grossa fetta di memoria, dal momento che non verranno mai raccolti.

È anche possibile consultare i riferimenti deboli e altre classi di gestione della memoria specializzate.

Infine, tenere a mente, che se la domanda riprende la memoria, ci potrebbe essere una ragione per questo ....

Aggiornamento La chiave per la gestione della memoria è le strutture di dati, così come la quantità di prestazioni di cui hai bisogno/quando. Il compromesso è spesso tra i cicli della memoria e della CPU.

Ad esempio, un sacco di memoria può essere occupato dalla memorizzazione nella cache, che è specificamente lì per migliorare le prestazioni poiché si sta tentando di evitare un'operazione costosa.

Quindi pensa attraverso le strutture dati e assicurati di non tenere le cose in memoria più a lungo di quanto tu non abbia. Se si tratta di un'app Web, evitare di memorizzare molti dati nella variabile di sessione, evitare di avere riferimenti statici a enormi pool di memoria, ecc.

4

Il primo ciclo è migliore. Perché

  • la variabile qualcosa sarà chiaro più veloce (teorico)
  • il programma è meglio leggere.

Ma dal punto di memoria questo è irrilevante.

Se si verificano problemi di memoria, è necessario definire il profilo in cui viene consumato.

+0

Siamo spiacenti, correggimi se ho torto, ma il primo ciclo alloca memoria per una nuova variabile su ciascun ciclo. Se il ciclo viene eseguito 100 volte, come mai è meglio creare 100 stringhe invece di sovrascrivere lo stesso e allocare memoria per una sola stringa (come nel secondo ciclo)? – Mapisto

+0

La memoria sullo stack per tutte le variabili locali di un metodo viene allocata sulla voce del metodo e non sul punto di dichiarazione nel codice. Le dimensioni di tale variabile sono 4 byte. (possibili 8 byte su VM a 64 bit). La dimensione della stringa è allocata nell'heap e non nello stack. Questa allocazione si verifica sulla parola chiave "nuova". Non c'è differenza nell'assegnazione delle stringhe. – Horcrux7

8

Non ci sono oggetti creati in entrambi gli esempi di codice. Basta impostare un riferimento a un oggetto che si trova già in arrayOfStuff. Quindi a memoria non c'è differenza.

1

Bene, il primo ciclo è effettivamente migliore, perché la portata di qualcosa è più piccola. Per quanto riguarda la gestione della memoria, non fa una grande differenza.

La maggior parte dei problemi di memoria Java viene quando si memorizzano oggetti in una raccolta, ma si dimentica di rimuoverli. Altrimenti il ​​GC rende il suo lavoro abbastanza buono.

1

Il primo esempio va bene. Non c'è nessuna allocazione di memoria in corso lì, a parte un'assegnazione di variabili e deallocazioni di stack ogni volta attraverso il ciclo (molto economico e veloce).

Il motivo è che tutto ciò che viene "allocato" è un riferimento, che è una variabile di stack da 4 byte (comunque sulla maggior parte dei sistemi a 32 bit). Una variabile stack è 'allocata' aggiungendo a un indirizzo di memoria che rappresenta la parte superiore dello stack, e quindi è molto veloce ed economica.

Quello che dovete stare attenti è per cicli come:

for (int i = 0; i < some_large_num; i++) 
{ 
    String something = new String(); 
    //do stuff with something 
} 

come quello sta effettivamente facendo memoria allocatiton.

+0

Non c'è nemmeno l'allocazione nello stack per ogni iterazione. Questi vengono allocati nella tabella delle variabili locali del metodo (una volta), tuttavia la variabile passa automaticamente fuori campo quando termina il ciclo. –

+0

sì, ma viene creato un nuovo oggetto String ogni iterazione che è più costoso di un'allocazione dello stack. – workmad3

5

La JVM è la migliore per liberare oggetti di breve durata. Cerca di non allocare oggetti che non ti servono. Ma non è possibile ottimizzare l'utilizzo della memoria finché non si capisce il carico di lavoro, la durata dell'oggetto e le dimensioni dell'oggetto. Un profiler può dirti questo.

Infine, la cosa numero 1 da evitare: non utilizzare mai i Finalizzatori.I finalizzatori interferiscono con la garbage collection, poiché l'oggetto non può essere solo liberato ma deve essere accodato per la finalizzazione, che può o non può accadere. È meglio non usare mai i finalizzatori.

Per quanto riguarda l'utilizzo della memoria visualizzato in Eclipse, non è necessariamente pertinente. Il GC farà il suo lavoro basandosi su quanta memoria libera ci sia. Se hai molta memoria libera potresti non vedere un singolo GC prima che l'app venga chiusa. Se trovi che la tua app sta esaurendo la memoria, solo un vero profiler può dirti dove sono le perdite o le inefficienze.

+0

Nei linguaggi object-oriented, garbage collection, le prestazioni scadenti sono spesso causate da un numero elevato di oggetti longevi, specialmente se ci sono molti livelli di composizione. Ad esempio, se creo un oggetto Spreadsheet, con una matrice 2D di oggetti Cell, in cui ogni oggetto contiene un oggetto Color, un oggetto Value e un oggetto Formatting e ogni oggetto Formatting contiene ... Bene, hai un'idea. Ci vorrà molto tempo prima che il GC gestisca il grafico dell'oggetto. Anche gli strati di memoria indiretta causano lag (lo fa in Smalltalk, comunque. Non sono sicuro di Java). – ahoffer

1

Se non lo hai già, ti suggerisco di installare il Eclipse Test & Performance Tools Platform (TPTP). Se si desidera eseguire il dump e ispezionare l'heap, consultare gli strumenti SDK jmap and jhat. Vedi anche Monitoring and Managing Java SE 6 Platform Applications.

+1

Dato che TPTP sembra ormai perfettamente funzionante (5 anni dopo), vai su jvisualvm che viene fornito con JDK e analizza i log gc (http://www.tagtraum.com/gcviewer.html o https://github.com/chewiebug/GCViewer). –

4

A mio parere, è necessario evitare micro-ottimizzazioni come queste. Costano molti cicli cerebrali, ma la maggior parte delle volte ha un impatto minimo.

L'applicazione ha probabilmente alcune strutture dati centrali. Questi sono quelli di cui dovresti essere preoccupato. Ad esempio, se li si riempie, li si prealloca con una buona stima della dimensione, per evitare il ripetuto ridimensionamento della struttura sottostante. Questo vale in particolare per StringBuffer, ArrayList, HashMap e simili. Progetta bene il tuo accesso a queste strutture, quindi non devi copiare molto.

Utilizzare gli algoritmi corretti per accedere alle strutture di dati. Al livello più basso, come il ciclo che hai citato, usa Iterator s, o almeno evita di chiamare lo .size() tutto il tempo. (Sì, stai chiedendo l'elenco ogni volta per le sue dimensioni, che la maggior parte delle volte non cambia.) A proposito, ho visto spesso un errore simile con Map s. La gente itera su keySet() e get ogni valore, invece di ripetere semplicemente il numero entrySet() in primo luogo. Il gestore della memoria ti ringrazierà per i cicli aggiuntivi della CPU.

2

Come suggerito da un poster sopra, utilizzare il profiler per misurare l'utilizzo della memoria (e/o della CPU) di alcune parti del programma piuttosto che cercare di indovinarlo. Potresti essere sorpreso da ciò che trovi!

C'è anche un ulteriore vantaggio. Comprenderete di più sul vostro linguaggio di programmazione e sulla vostra applicazione.

Uso VisualVM per la creazione di profili e lo consiglio molto. Viene fornito con la distribuzione jdk/jre.

0

"Sono errato per dire che il ciclo inferiore è meglio?", La risposta è NO, non solo è meglio, nello stesso caso è necessario ... La definizione della variabile (non il contenuto), è fatta in memoria heap, ed è limitato, nel primo esempio, ogni loop crea un'istanza in questa memoria, e se la dimensione di "arrayOfStuff" è grande, può verificarsi "Memoria insufficiente: java heap space" ....

0

Da quello che capisco stai guardando, il ciclo inferiore non è migliore. Il motivo è che anche se si sta tentando di riutilizzare un riferimento singolo (Ex- qualcosa), il fatto è che l'oggetto (Ex-arrayOfStuff.get (i)) è ancora referenziato dall'elenco (arrayOfStuff). Per rendere gli oggetti idonei per la raccolta, non dovrebbero essere referenziati da nessun luogo. Se sei sicuro della vita della Lista dopo questo punto, puoi decidere di rimuovere/liberare gli oggetti da esso, all'interno di un ciclo separato.

L'ottimizzazione che può essere eseguita da una prospettiva statica (ovvero nessuna modifica sta accadendo a questo Elenco da qualsiasi altro thread), è meglio evitare ripetutamente l'invocazione di size(). Cioè se non ti aspetti che le dimensioni cambino, allora perché calcolarlo ancora e ancora; dopotutto non è un array.length, è list.size().

Problemi correlati