Terminologia della memoria

Meggin Kearney
Meggin Kearney

Questa sezione descrive i termini comuni utilizzati nell'analisi della memoria ed è applicabile a una varietà di strumenti di profilazione della memoria in lingue diverse.

I termini e le nozioni descritti qui fanno riferimento a Chrome DevTools Heap Profiler. Se hai mai lavorato con Java, .NET o qualche altro strumento di profilazione della memoria, questo potrebbe essere un ripasso.

Dimensioni degli oggetti

Pensa alla memoria come a un grafico con tipi primitivi (come numeri e stringhe) e oggetti (array associati). Potrebbe essere rappresentato visivamente come un grafico con un numero di punti interconnessi come segue:

Rappresentazione visiva della memoria

Un oggetto può contenere la memoria in due modi:

  • Direttamente dall'oggetto stesso.
  • In modo implicito, include i riferimenti ad altri oggetti, impedendo quindi lo smaltimento automatico di questi oggetti da parte di un garbage collector (GC).

Quando utilizzi lo strumento Heap Profiler in DevTools (uno strumento per analizzare i problemi di memoria che si trovano nella sezione "Profili"), probabilmente ti ritroverai a esaminare diverse colonne di informazioni. Due che si distinguono sono la dimensione superficiale e la dimensione conservata, ma cosa rappresentano?

Dimensioni superficiali e conservate

Dimensioni ridotte

Si tratta della dimensione della memoria trattenuta dall'oggetto stesso.

Gli oggetti JavaScript tipici hanno una parte di memoria riservata alla loro descrizione e all'archiviazione di valori immediati. In genere, solo array e stringhe possono avere una dimensione ridotta significativa. Tuttavia, le stringhe e gli array esterni spesso hanno la loro memoria principale nella memoria del renderer, esponendo solo un oggetto wrapper piccolo nell'heap JavaScript.

La memoria del renderer è tutta la memoria del processo in cui viene visualizzata una pagina ispezionata: memoria nativa + memoria heap JS della pagina + memoria heap JS di tutti i worker dedicati avviati dalla pagina. Tuttavia, anche un piccolo oggetto può contenere indirettamente una grande quantità di memoria, impedendo che altri oggetti vengano eliminati dal processo automatico di garbage collection.

Dimensioni conservate

Indica le dimensioni della memoria liberata dopo che l'oggetto stesso viene eliminato insieme agli oggetti dipendenti che sono stati resi non raggiungibili dalle radici GC.

Le radici GC sono costituite da handle che vengono creati (locali o globali) quando si fa un riferimento dal codice nativo a un oggetto JavaScript esterno a V8. Tutti questi handle sono disponibili in uno snapshot heap in Radici GC > Ambito degli handle e Radici GC > Handle globali. La descrizione degli handle in questa documentazione senza scendere nei dettagli dell'implementazione del browser potrebbe creare confusione. Sia i certificati radice GC che gli handle non sono elementi di cui ti devi preoccupare.

Esistono molte radici di GC interne la maggior parte delle quali non sono interessanti per gli utenti. Dal punto di vista delle applicazioni, esistono i seguenti tipi di radici:

  • Oggetto globale della finestra (in ogni iframe). Negli snapshot heap è presente un campo di distanza che rappresenta il numero di riferimenti alla proprietà nel percorso di conservazione più breve dalla finestra.
  • Documenta l'albero DOM costituito da tutti i nodi DOM nativi raggiungibili attraversando il documento. Non tutti possono avere wrapper JS ma, se li contengono, rimarranno attivi mentre il documento è attivo.
  • A volte gli oggetti potrebbero essere conservati dal contesto del debugger e dalla console DevTools (ad es. dopo la valutazione della console). Crea snapshot heap con console vuota e nessun punto di interruzione attivo nel debugger.

Il grafico della memoria inizia con una radice, che può essere l'oggetto window del browser o l'oggetto Global di un modulo Node.js. Non puoi controllare come viene assegnato questo oggetto principale a GC.

L'oggetto root non può essere controllato

Tutto ciò che non è raggiungibile dalla radice ottiene GC.

Oggetti che mantengono l'albero

L'heap è una rete di oggetti interconnessi. Nel mondo matematico, questa struttura è chiamata grafico o grafico di memoria. Un grafico viene creato a partire da nodi collegati tra loro per mezzo di archi, entrambi provvisti di etichette.

  • I nodi (o oggetti) vengono etichettati utilizzando il nome della funzione constructor utilizzata per crearli.
  • I bordi vengono etichettati utilizzando i nomi delle proprietà.

Scopri come registrare un profilo utilizzando Heap Profiler. Alcune delle cose interessanti che possiamo vedere nella registrazione di Heap Profiler di seguito includono la distanza: la distanza dalla radice GC. Se quasi tutti gli oggetti dello stesso tipo sono alla stessa distanza e alcuni si trovano a una distanza maggiore, vale la pena esaminare.

Distanza dalla radice

Dominatori

Gli oggetti dominatori sono costituiti da una struttura ad albero perché ogni oggetto ha esattamente un dominatore. Un dominatore di un oggetto può non avere riferimenti diretti all'oggetto che domina; in altre parole, l'albero del dominatore non è un albero ricoprente del grafico.

Nel diagramma seguente:

  • Il nodo 1 domina il nodo 2
  • Il nodo 2 domina i nodi 3, 4 e 6
  • Il nodo 3 domina il nodo 5
  • Il nodo 5 domina il nodo 8
  • Il nodo 6 domina il nodo 7

Struttura ad albero dominante

Nell'esempio seguente, il nodo #3 è il dominatore di #10, ma #7 esiste anche in ogni percorso semplice da GC a #10. Pertanto, un oggetto B è un dominatore di un oggetto A se esiste in ogni percorso semplice dalla radice all'oggetto A.

Illustrazione dominatrice animata

Specifiche della versione V8

Quando esegui la profilazione della memoria, è utile comprendere perché gli snapshot dell'heap hanno un determinato aspetto. Questa sezione descrive alcuni argomenti relativi alla memoria corrispondenti in modo specifico alla macchina virtuale JavaScript V8 (VM V8 o VM).

Rappresentazione dell'oggetto JavaScript

Esistono tre tipi primitivi:

  • Numeri (ad es. 3,14159..)
  • Operatori booleani (true o false)
  • Stringhe (ad es. "Werner Heisenberg")

Non possono fare riferimento ad altri valori e sono sempre foglia o nodi di terminazione.

I numeri possono essere memorizzati come:

  • valori interi immediati a 31 bit denominati numeri interi (SMI) o
  • oggetti heap, detti numeri heap. I numeri heap vengono utilizzati per archiviare i valori che non rientrano nel modulo SMI, ad esempio i doppi, o quando un valore deve essere riportato in riquadri, ad esempio per impostarne le proprietà.

Le stringhe possono essere archiviate in:

  • l'heap della VM o
  • esternamente nella memoria del rendering. Un oggetto wrapper viene creato e utilizzato per accedere all'archiviazione esterna in cui, ad esempio, vengono archiviate le origini degli script e altri contenuti ricevuti dal web, anziché copiati nell'heap della VM.

La memoria per i nuovi oggetti JavaScript viene allocata da un heap JavaScript dedicato (o heap di VM). Questi oggetti sono gestiti dal garbage collector di V8, pertanto rimarranno attivi finché vi è almeno un riferimento forte.

Gli oggetti nativi sono tutto il resto che non è presente nell'heap JavaScript. L'oggetto nativo, in contrasto con l'oggetto heap, non viene gestito dal garbage collector V8 per tutta la sua durata ed è accessibile solo da JavaScript utilizzando il relativo oggetto wrapper JavaScript.

La stringa Cons è un oggetto costituito da coppie di stringhe archiviate e poi unite ed è il risultato della concatenazione. L'unione dei contenuti della stringa cons avviene solo in base alle necessità. Un esempio potrebbe essere quando deve essere creata una sottostringa di una stringa unita.

Ad esempio, se concateni a e b, ottieni una stringa (a, b) che rappresenta il risultato della concatenazione. Se in seguito hai concatenato d con questo risultato, otterrai un'altra stringa cons ((a, b), d).

Array: un array è un oggetto con chiavi numeriche. Sono ampiamente utilizzati nella VM V8 per archiviare grandi quantità di dati. Gli insiemi di coppie chiave-valore utilizzate come i dizionari vengono sottoposti a backup da array.

Un oggetto JavaScript tipico può essere uno dei due tipi di array utilizzati per l'archiviazione:

  • proprietà denominate e
  • elementi numerici

Se il numero di proprietà è molto ridotto, è possibile archiviarle internamente nell'oggetto JavaScript stesso.

Mappa: un oggetto che descrive il tipo di oggetto e il suo layout. Ad esempio, le mappe vengono utilizzate per descrivere le gerarchie di oggetti implicite per un accesso rapido alle proprietà.

Gruppi di oggetti

Ogni gruppo di oggetti nativi è composto da oggetti che contengono riferimenti reciproci. Considera, ad esempio, un sottoalbero DOM in cui ogni nodo ha un link al relativo elemento padre e link al figlio successivo e al prossimo elemento di pari livello, formando così un grafico collegato. Tieni presente che gli oggetti nativi non sono rappresentati nell'heap JavaScript e per questo motivo hanno dimensioni pari a zero. Vengono invece creati gli oggetti wrapper.

Ogni oggetto wrapper contiene un riferimento all'oggetto nativo corrispondente per il reindirizzamento dei comandi. Un gruppo di oggetti contiene a sua volta gli oggetti wrapper. Tuttavia, questo non crea un ciclo non raccoglibile, poiché GC è sufficientemente intelligente da rilasciare gruppi di oggetti i cui wrapper non sono più riportati. Se dimentichi di rilasciare un singolo wrapper, però, l'intero gruppo e i wrapper associati verranno conservati.