Questo articolo è la continuazione dell'articolo “i 4 tipi di memory leak”

Il comportamento non intuitivo dei netturbini
Sebbene i raccoglitori di rifiuti siano molto pratici, arrivano con la loro parte di compromessi. Uno di questi compromessi è il loro non determinismo. In altre parole, i raccoglitori di rifiuti sono imprevedibili. In genere non è possibile determinare con certezza quando verrà effettuata una raccolta dei rifiuti. Ciò implica che in alcuni casi il programma utilizza più memoria del necessario. In altri casi, si possono notare brevi interruzioni nelle applicazioni sensibili. Sebbene non deterministico significhi che non si può mai essere sicuri di quando verrà eseguita la raccolta, la maggior parte delle implementazioni di GC condivide un modello comune di esecuzione della raccolta durante l'allocazione. Quando non viene effettuata alcuna allocazione, la maggior parte dei GS rimane inattiva.
Considera il seguente scenario:
- Viene effettuato un conseguente insieme di stanziamenti
- La maggior parte (o tutti) di questi elementi sono contrassegnati come inaccessibili (immagina di annullare un riferimento che punta a una cache che non è più necessaria)
- Non vengono più concesse indennità
In questo scenario, la maggior parte dei GC non raccoglierà più. Cioè, anche se ci sono oggetti inaccessibili disponibili per il ritiro, non vengono rivendicati dal collezionista. Queste non sono perdite di per sé, ma il risultato è superiore al normale utilizzo della memoria.
Google fornisce un ottimo esempio di questo comportamento nei loro documenti JavaScript Memory Profiling, esempio n. 2.
Presentazione degli strumenti di profilatura della memoria di Chrome
Chrome fornisce un buon set di strumenti per la diagnosi dell'utilizzo della memoria del codice JavaScript. Esistono principalmente due viste relative alla memoria: la vista della sequenza temporale e la vista del profilo.
Vista della sequenza temporale
La visualizzazione della sequenza temporale è essenziale per scoprire schemi di memoria insoliti nel nostro codice. Quando cerchiamo una grossa perdita, i salti periodici che non si riducono tanto quanto sono cresciuti dopo la raccolta dovrebbero allarmarvi. In questa cattura, vediamo come appare la crescita costante di un oggetto che perde. Anche dopo la grande raccolta finale, la quantità totale di memoria utilizzata è maggiore alla fine rispetto all'inizio. Anche il numero di nodi. Così tanti accenni di perdite di DOM da qualche parte nel codice.
Vista profilo
È questa vista che trascorrerai molto tempo a guardare. La vista profili ti consente di ottenere un'istantanea e confrontare le istantanee dell'utilizzo della memoria del tuo codice JavaScript.
Ciò consente di risparmiare allocazioni nel tempo. In tutte le visualizzazioni dei risultati esistono diversi tipi di elenchi, ma i più rilevanti per il nostro compito sono l'elenco di riepilogo e l'elenco di confronto.
La vista di riepilogo fornisce una panoramica dei diversi tipi di oggetti allocati e delle loro dimensioni aggregate: dimensione ridotta (la somma di tutti gli oggetti di un tipo specifico) e dimensione mantenuta (la dimensione ridotta più la dimensione di altri oggetti mantenuti a causa di questo oggetto ). Questo dà anche un'idea della distanza di questo oggetto dalla sua radice GC (la distanza).
L'elenco di confronto ci fornisce le stesse informazioni ma ci consente di confrontare le diverse istantanee. Ciò è particolarmente utile per trovare perdite.
Esempio: ricerca di perdite utilizzando Chrome
Esistono principalmente due tipi di perdite: perdite che causano aumenti periodici dell'utilizzo della memoria e perdite che si verificano una volta e non causano ulteriori aumenti dell'utilizzo della memoria. Per ovvi motivi, è più facile trovare perdite quando sono periodiche. Sono anche i più problematici, se la memoria aumenta nel tempo, perdite di questo tipo possono rallentare il browser o causare l'arresto dello script. Le perdite non periodiche possono essere facili da trovare quando sono abbastanza grandi da essere notate tra le altre allocazioni. Di solito non è così, quindi passano inosservati. In un certo senso, le perdite che si verificano solo una volta potrebbero essere classificate come problemi di ottimizzazione. Detto questo, le perdite periodiche sono bug e dovrebbero essere risolte.
Per illustrare, prendiamo un esempio nel documento di Chrome. Il codice è copiato di seguito:
var x = [];
function createSomeNodes() {
var div,
i = 100,
frag = document.createDocumentFragment();
for (;i > 0; i--) {
div = document.createElement("div");
div.appendChild(document.createTextNode(i + " - "+ new Date().toTimeString()));
frag.appendChild(div);
}
document.getElementById("nodes").appendChild(frag);
}
function grow() {
x.push(new Array(1000000).join('x'));
createSomeNodes();
setTimeout(grow,1000);
}
Quando viene chiamato grow, inizierà creando nodi DIV e collegandoli al DOM. Assegnerà anche un array di grandi dimensioni e vi aggiungerà un array a cui fa riferimento una variabile globale. Ciò causerà un aumento costante della memoria che può essere trovato utilizzando gli strumenti sopra menzionati.
Generalmente osserviamo un modello di utilizzo della memoria oscillante nei linguaggi basati su Garbage Collection. Questo è ciò che ci si aspetta se il codice viene eseguito su un ciclo che produce le allocazioni, come di solito accade. Cercheremo aumenti periodici della memoria che non ricadano ai livelli precedenti dopo la raccolta.
Per prima cosa, controlla se la memoria aumenta periodicamente.
La sequenza temporale di View è ottima per questo. Apri l'esempio in Chrome, apri Dev Tools, vai alla timeline, seleziona la memoria e fai clic sul pulsante di registrazione. Quindi vai alla pagina e fai clic su The Button per avviare la perdita di memoria. Aspetta un po', quindi interrompi la registrazione e guarda il risultato:
Perdita di memoria nella visualizzazione della sequenza temporale
Questo esempio continuerà a perdere memoria ogni secondo. Dopo aver interrotto la registrazione, aggiungi un punto di interruzione nella funzione di crescita per impedire allo script di forzare Chrome a chiudere la pagina. Ci sono 2 grandi indizi in questa immagine che mostrano che abbiamo una perdita di memoria. I grafici dei nodi (linea verde) e dell'heap JS (linea blu). I nodi aumentano costantemente e non scendono mai. Questa è una grande bandiera rossa.
JS Heap mostra anche un aumento costante dell'utilizzo della memoria. È più difficile da vedere a causa degli effetti del Garbage Collector. È possibile osservare uno schema di crescita iniziale della memoria, seguito da una forte riduzione, seguita da una crescita, quindi da un picco, seguito da un'altra riduzione. La chiave in questo caso è che dopo ogni riduzione dell'utilizzo della memoria, la dimensione dell'heap rimane maggiore rispetto alla volta precedente. Ciò significa che sebbene il Garbage Collector recuperi correttamente molta memoria, parte viene regolarmente persa.
Ora siamo certi che ci sia una perdita. Troviamolo.

Scatta due istantanee
Per trovare la perdita, andremo ora alla sezione del profilo di Chrome Dev Tools. per mantenere l'utilizzo della memoria a un livello gestibile, ricaricare la pagina prima di questo passaggio. Utilizzeremo la funzione Take Heap Snapshot.
Ricarica la pagina e scatta un'istantanea dell'heap subito dopo aver terminato il caricamento. Useremo questa istantanea come riferimento. Ora fai di nuovo clic sul pulsante, attendi qualche secondo e scatta una nuova istantanea. Dopo aver eseguito lo snapshot, è consigliabile inserire un punto di interruzione nello script per evitare che la perdita consumi più memoria.
Istantanee dell'heap
Esistono due modi per esaminare le allocazioni tra due snapshot. O fai clic su Riepilogo e poi vai a destra e seleziona Oggetti allocati tra Istantanea 1 e Istantanea 2, oppure fai clic su Confronto invece di Riepilogo. In entrambi i casi, vedremo un elenco di oggetti che sono stati allocati tra i due snapshot.
In questo caso, è abbastanza facile trovare le perdite: sono grandi. Guarda il Size Delta del costruttore (stringa). 8 MB per 58 nuovi articoli. Sembra sospetto: nuovi oggetti vengono allocati ma non liberati e vengono consumati 8 MB.
Se apriamo l'elenco delle allocazioni per il costruttore (stringa), noteremo che ci sono alcune allocazioni grandi tra molte allocazioni piccole. I grandi catturano immediatamente la nostra attenzione. Se ne selezioniamo qualcuno, troveremo qualcosa di interessante con esso nella sezione Retainer appena sotto.
Fermi per l'oggetto selezionato
Vediamo che l'allocazione selezionata fa parte di un array. A sua volta, l'array è referenziato dalla variabile x nell'oggetto finestra globale. Questo ci dà il percorso completo dal nostro grande oggetto alla sua radice non collezionabile (finestra). Abbiamo trovato la nostra potenziale perdita e dove è referenziata.
Fin qui tutto bene. Ma il nostro esempio è stato facile: grandi allocazioni come nell'esempio non sono la norma. Fortunatamente, il nostro esempio perde anche i nodi DOM, che sono più piccoli. È facile trovare questi nodi usando l'istantanea sopra, ma nei siti più grandi le cose si complicano. Le versioni recenti di Chrome forniscono uno strumento aggiuntivo che ben si adatta al nostro lavoro: la funzione Record Heap Allocations.
Salva l'allocazione dell'heap per trovare le perdite
Disattiva il punto di interruzione che hai impostato in precedenza, lascia che lo script venga eseguito e torna alla sezione Profilo di Chrome Dev Tools. Ora tocca Registra allocazioni heap. Durante l'esecuzione dello strumento, noterai dei picchi blu nel grafico in alto. rappresenta le indennità. Ogni secondo, il codice produce una grossa allocazione. Lascialo funzionare per alcuni secondi, quindi interrompilo (non dimenticare di aggiungere un punto di interruzione per impedire a Chrome di consumare più memoria).
Heap quote salvate.
In questa immagine, puoi vedere la caratteristica killer di questo strumento: seleziona una parte della sequenza temporale per vedere quali allocazioni sono state effettuate durante quel periodo. Definiamo la selezione più vicina ai grandi picchi. Nell'elenco vengono visualizzati solo tre costruttori. Uno di questi è quello relativo alla nostra grande perdita ((string)), il successivo è relativo alle allocazioni DOM e l'ultimo è il costruttore Text (il costruttore per i nodi foglia DOM contenenti testo).
Selezionare uno dei costruttori HTMLDivElement dall'elenco, quindi fare clic su seleziona Stack di allocazione.
Elemento selezionato nei risultati dell'allocazione dell'heap
BAM! Ora sappiamo dove è stato allocato questo elemento (grow -> createSomeNodes). Se osserviamo attentamente ogni picco nel grafico, noteremo che il costruttore HTMLDivElement è chiamato molto. Se torniamo alla vista di confronto Snapshot, noteremo che questo costruttore rivela molta allocazione ma nessuna eliminazione. In altre parole, alloca regolarmente memoria senza consentire al GC di recuperarne. Questi sono tutti sintomi di una perdita e, inoltre, sappiamo esattamente dove sono allocati questi oggetti (la funzione createSomeNodes). Ora è il momento di tornare al codice, studiarlo e correggere le perdite.
- Un'altra caratteristica utile:
Nella visualizzazione dei risultati delle allocazioni dell'heap, è possibile selezionare Visualizzazione allocazione anziché Riepilogo.
Questa vista fornisce un elenco di funzioni e le relative allocazioni di memoria. Possiamo immediatamente vedere crescere e creareSomeNodes spiccare. Quando selezioniamo crescere, diamo un'occhiata ai costruttori degli oggetti associati che vengono chiamati da esso. Notiamo (string), HTMLDivElement e Text che già sappiamo essere i costruttori degli oggetti che perdono.
La combinazione di questi strumenti può fare molto per trovare perdite. Giocaci, esegui profili diversi nei tuoi siti di produzione (idealmente codice, non minimizzato o offuscato). Vedi se riesci a trovare eventuali perdite o oggetti che vengono conservati più a lungo del dovuto (suggerimento: sono più difficili da trovare).
Per utilizzare queste funzionalità, vai su Strumenti di sviluppo -> Impostazioni e abilita "registra le tracce dello stack di allocazione dell'heap". È necessario farlo prima della registrazione.
- Per andare oltre:
Gestione della memoria: rete di sviluppatori Mozilla
Perdite di memoria JScript - Douglas Crockford (vecchio, in relazione a perdite di Internet Explorer 6)
Profilazione memoria JavaScript – Documenti per sviluppatori di Chrome
Diagnosi della memoria – Sviluppatori Google
Un tipo interessante di perdita di memoria JavaScript – Blog di Meteor
Chiusure Grokking V8
Conclusione
Le perdite di memoria possono e si verificano nei linguaggi di garbage collection come JavaScript. Possono vivere nascosti per un po' e alla fine creare scompiglio. Per questo motivo, gli strumenti di profilazione della memoria sono essenziali per trovare perdite di memoria. La profilazione dovrebbe far parte dei cicli di sviluppo, soprattutto per applicazioni di dimensioni medio-grandi. Inizia ora per offrire ai tuoi utenti la migliore esperienza possibile.
Buon debug!
Trova il primo articolo su questo argomento qui !
[tipo separatore=”” size=”” icon=”stella”] [actionbox color=”default” title=”” description=”JS-REPUBLIC è una società di servizi specializzata nello sviluppo di JavaScript. Siamo un centro di formazione riconosciuto. Trova tutta la nostra formazione tecnica sul nostro sito partner dedicato alla Formazione” btn_label=”La nostra formazione” btn_link=”http://training.ux-republic.com” btn_color=”primary” btn_size=”big” btn_icon=”star” btn_external =”1″]
