Questa sezione descrive i termini comuni utilizzati nell'analisi della memoria ed è applicabile a una serie di strumenti di profilazione della memoria per lingue diverse.
I termini e le nozioni qui descritti si riferiscono allo strumento Heap Profiler di Chrome DevTools. Se in passato hai lavorato con Java, .NET o un altro profiler di 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 associativi). Potrebbe essere visivamente rappresentato come un grafico con una serie di punti interconnessi come segue:
Un oggetto può contenere la memoria in due modi:
- Direttamente dall'oggetto stesso.
- implicitamente conservando riferimenti ad altri oggetti e impedendo quindi che questi oggetti vengano eliminati automaticamente da un garbage collector (GC).
Quando lavori con Heap Profiler in DevTools (uno strumento per esaminare i problemi di memoria che si trovano in "Profili"), ti ritroverai probabilmente a esaminare alcune colonne di informazioni diverse. Due che si distinguono sono Shallow Size e Conservati Dimensione, ma cosa rappresentano?
Dimensioni poco profonde
Si tratta della dimensione della memoria contenuta dall'oggetto stesso.
Gli oggetti JavaScript tipici hanno una memoria riservata per la descrizione e l'archiviazione di valori immediati. Di solito, solo gli array e le stringhe possono avere dimensioni minime significative. Tuttavia, le stringhe e gli array esterni hanno spesso la propria archiviazione principale nella memoria del renderer, esponendo solo un piccolo oggetto wrapper sull'heap JavaScript.
La memoria del renderer è tutta la memoria del processo in cui viene eseguito il rendering di una pagina ispezionata: memoria nativa + memoria heap JS della pagina + memoria heap JS di tutti i worker dedicati avviati dalla pagina. Tuttavia, anche un oggetto di piccole dimensioni può contenere indirettamente una grande quantità di memoria, impedendo lo smaltimento di altri oggetti da parte del processo di garbage collection automatico.
Dimensione mantenuta
Questa è la dimensione della memoria liberata dopo che l'oggetto stesso viene eliminato insieme agli oggetti dipendenti che sono stati resi irraggiungibili dalle radici di GC.
Le radici di GC sono costituite da handle creati (locali o globali) quando si fa un riferimento da codice nativo a un oggetto JavaScript al di fuori di V8. Tutti questi handle sono disponibili all'interno di uno snapshot dell'heap in Root GC > Ambito dell'handle e Root GC > Handle globali. La descrizione degli handle in questa documentazione senza approfondire i dettagli dell'implementazione del browser potrebbe creare confusione. Non devi preoccuparti sia dei root GC che degli handle.
Esistono molte radici GC interne, molte delle quali non sono interessanti per gli utenti. Dal punto di vista delle applicazioni, esistono i seguenti tipi di radici:
- Oggetto globale finestra (in ogni iframe). Negli snapshot dell'heap è presente un campo distanza, ovvero il numero di riferimenti alle proprietà nel percorso di conservazione più breve dalla finestra.
- Struttura DOM di documenti composta da tutti i nodi DOM nativi raggiungibili attraversando il documento. Non tutti possono avere wrapper JS, ma se li hanno rimangono attivi finché il documento è attivo.
- A volte gli oggetti potrebbero essere conservati dal contesto del debugger e dalla console DevTools (ad esempio dopo la valutazione della console). Crea snapshot dell'heap con una console chiara 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 il modo in cui viene assegnato a questo oggetto principale in GC.
Ciò che non è raggiungibile dalla radice ottiene GC.
Albero che conserva gli oggetti
L'heap è una rete di oggetti interconnessi. Nel mondo matematico, questa struttura è chiamata grafico o grafico della memoria. Un grafico viene creato da nodi collegati per mezzo di bordi, a entrambi assegnati etichetta.
- I nodi (o oggetti) sono etichettati utilizzando il nome della funzione costruttore utilizzato per crearli.
- I bordi vengono etichettati utilizzando i nomi delle proprietà.
Scopri come registrare un profilo utilizzando Heap Profiler. Alcune cose interessanti che possiamo vedere nella registrazione di Heap Profiler di seguito includono la distanza dalla radice di GC. Se quasi tutti gli oggetti dello stesso tipo si trovano alla stessa distanza, e alcuni si trovano a una distanza maggiore, vale la pena indagare.
Dominatori
Gli oggetti Dominator sono costituiti da una struttura ad albero perché ogni oggetto ha esattamente un dominio. Un dominio di un oggetto potrebbe non avere riferimenti diretti a un oggetto che domina; ovvero, l'albero del dominio non è un albero ampliante del grafo.
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
Nell'esempio seguente, il nodo #3
è il dominio 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 B esiste in ogni semplice
percorso dalla radice all'oggetto A.
Specifiche V8
Durante la profilazione della memoria, è utile comprendere perché gli snapshot dell'heap hanno un determinato aspetto. In questa sezione vengono descritti alcuni argomenti relativi alla memoria che corrispondono in modo specifico alla macchina virtuale JavaScript V8 (VM o VM V8).
Rappresentazione dell'oggetto JavaScript
Esistono tre tipi primitivi:
- Numeri (ad es. 3,14159..)
- Booleani (true o false)
- Stringhe (ad es. "Werner Heisenberg")
Non possono fare riferimento ad altri valori e sono sempre foglie o nodi finali.
I numeri possono essere memorizzati in:
- valori interi immediati a 31 bit denominati piccoli numeri interi (SMI) oppure
- Oggetti heap, chiamati numeri heap. I numeri heap vengono utilizzati per archiviare i valori che non rientrano nel modulo SMI, ad esempio doppio, o quando un valore deve essere in scatola, ad esempio per impostare le proprietà.
Le stringhe possono essere archiviate in:
- l'heap della VM oppure
- all'esterno, nella memoria del rendering. Un oggetto wrapper viene creato e utilizzato per accedere all'unità di archiviazione esterna dove, ad esempio, le origini degli script e altri contenuti ricevuti dal web vengono archiviati, anziché copiati nell'heap della VM.
La memoria per i nuovi oggetti JavaScript viene allocata da un heap JavaScript dedicato (o heap VM). Questi oggetti sono gestiti dal garbage collector di V8 e pertanto rimarranno attivi finché sarà presente almeno un riferimento efficace.
Gli oggetti nativi sono tutto ciò che non è presente nell'heap JavaScript. L'oggetto nativo, in contrasto con l'oggetto heap, non è gestito dal garbage collector V8 per tutta la sua durata e è accessibile solo da JavaScript utilizzando il relativo oggetto wrapper JavaScript.
La stringa contro è un oggetto composto da coppie di stringhe archiviate e poi unite ed è il risultato della concatenazione. L'unione dei contenuti della stringa cons avviene solo se necessario. 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, ottieni un'altra stringa contro ((a, b), d).
Array: un array è un oggetto con chiavi numeriche. Sono ampiamente utilizzate nella VM V8 per l'archiviazione di grandi quantità di dati. Gli insiemi di coppie chiave-valore utilizzate come dizionari vengono sottoposti a backup da array.
Un oggetto JavaScript tipico può essere uno dei due tipi di array utilizzati per l'archiviazione:
- proprietà con nome
- elementi numerici
Nei casi in cui il numero di proprietà sia molto ridotto, queste possono essere archiviate 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. Prendi ad esempio un sottoalbero del DOM in cui ogni nodo ha un collegamento al proprio padre e al successivo elemento secondario e al successivo elemento di pari livello, in modo da formare un grafico connesso. Nota che gli oggetti nativi non sono rappresentati nell'heap di JavaScript. Per questo motivo, hanno dimensioni pari a zero. Vengono invece creati oggetti wrapper.
Ogni oggetto wrapper contiene un riferimento all'oggetto nativo corrispondente per reindirizzare i comandi. A sua volta, un gruppo di oggetti contiene oggetti wrapper. Tuttavia, ciò non crea un ciclo non raccoglibile, poiché GC è abbastanza intelligente da rilasciare gruppi di oggetti i cui wrapper non sono più indicati. Se dimenticate di rilasciare un singolo wrapper, però, l'intero gruppo e i wrapper associati verranno conservati.