In deze sectie worden algemene termen beschreven die worden gebruikt bij geheugenanalyse, en is van toepassing op een verscheidenheid aan geheugenprofileringstools voor verschillende talen.
De hier beschreven termen en begrippen verwijzen naar Chrome DevTools Heap Profiler . Als u ooit met Java, .NET of een andere geheugenprofiler hebt gewerkt, dan is dit wellicht een opfrisser.
Objectgroottes
Beschouw geheugen als een grafiek met primitieve typen (zoals getallen en tekenreeksen) en objecten (associatieve arrays). Het kan visueel worden weergegeven als een grafiek met een aantal onderling verbonden punten, als volgt:
Een object kan op twee manieren geheugen vasthouden:
- Direct door het object zelf.
- Impliciet door verwijzingen naar andere objecten vast te houden en daardoor te voorkomen dat die objecten automatisch worden verwijderd door een garbage collector (kortweg GC ).
Wanneer u met de Heap Profiler in DevTools werkt (een hulpmiddel voor het onderzoeken van geheugenproblemen in het paneel Geheugen ), zult u waarschijnlijk naar een paar verschillende kolommen met informatie kijken. Twee die opvallen zijn Shallow Size en Retained Size , maar wat vertegenwoordigen deze?
Ondiep formaat
Dit is de grootte van het geheugen dat door het object zelf wordt vastgehouden.
Typische JavaScript-objecten hebben wat geheugen gereserveerd voor hun beschrijving en voor het opslaan van directe waarden. Gewoonlijk kunnen alleen arrays en strings een aanzienlijk geringe omvang hebben. Strings en externe arrays hebben hun hoofdopslag echter vaak in het geheugen van de renderer, waardoor slechts een klein wrapper-object op de JavaScript-heap zichtbaar wordt.
Renderergeheugen is al het geheugen van het proces waarbij een geïnspecteerde pagina wordt weergegeven: eigen geheugen + JS-heapgeheugen van de pagina + JS-heapgeheugen van alle toegewijde werkers die door de pagina zijn gestart. Niettemin kan zelfs een klein object indirect een grote hoeveelheid geheugen bevatten, door te voorkomen dat andere objecten worden verwijderd door het automatische afvalinzamelingsproces.
Behouden maat
Dit is de grootte van het geheugen dat vrijkomt zodra het object zelf wordt verwijderd, samen met de afhankelijke objecten die onbereikbaar zijn gemaakt vanaf de GC-roots .
GC-roots bestaan uit handvatten die worden gemaakt (lokaal of globaal) bij het maken van een verwijzing vanuit native code naar een JavaScript-object buiten V8. Al dergelijke handvatten zijn te vinden in een heap-momentopname onder GC-roots > Handle-bereik en GC-roots > Globale handvatten . Het kan verwarrend zijn om de handvatten in deze documentatie te beschrijven zonder in details van de browserimplementatie te duiken. Zowel de GC-wortels als de handgrepen zijn niet iets waar u zich zorgen over hoeft te maken.
Er zijn veel interne GC-wortels waarvan de meeste niet interessant zijn voor de gebruikers. Vanuit het oogpunt van toepassingen zijn er de volgende soorten wortels:
- Globaal vensterobject (in elk iframe). Er is een afstandsveld in de heap-snapshots, dit is het aantal eigenschapsreferenties op het kortste vasthoudpad vanaf het venster.
- Document-DOM-boom bestaande uit alle oorspronkelijke DOM-knooppunten die bereikbaar zijn door het document te doorlopen. Het kan zijn dat ze niet allemaal JS-wrappers hebben, maar als ze dat wel hebben, zullen de wrappers in leven blijven terwijl het document leeft.
- Soms kunnen objecten behouden blijven door de debuggercontext en de DevTools-console (bijvoorbeeld na console-evaluatie). Maak heap-snapshots met duidelijke console en zonder actieve breekpunten in de debugger.
De geheugengrafiek begint met een root, wat het window
van de browser kan zijn of het Global
object van een Node.js-module. U heeft geen controle over hoe dit hoofdobject via GC wordt behandeld.
Wat niet bereikbaar is vanaf de root, krijgt GC.
Objecten die de boom vasthouden
De heap is een netwerk van onderling verbonden objecten. In de wiskundige wereld wordt deze structuur een grafiek of geheugengrafiek genoemd. Een grafiek is opgebouwd uit knooppunten die met elkaar zijn verbonden door middel van randen , die beide een label hebben gekregen.
- Knooppunten ( of objecten ) worden gelabeld met de naam van de constructorfunctie die werd gebruikt om ze te bouwen.
- Randen worden gelabeld met de namen van eigenschappen .
Leer hoe u een profiel kunt opnemen met behulp van de Heap Profiler . Enkele van de in het oog springende dingen die we kunnen zien in de volgende Heap Profiler-opname zijn afstand: de afstand vanaf de GC-wortel. Als bijna alle objecten van hetzelfde type zich op dezelfde afstand bevinden, en een paar op een grotere afstand, is dat het onderzoeken waard.
Dominators
Dominator-objecten bestaan uit een boomstructuur omdat elk object precies één directe dominator heeft. Het kan zijn dat een dominator van een object geen directe verwijzingen heeft naar een object dat hij domineert; dat wil zeggen, de boom van de dominator is geen opspannende boom van de grafiek.
In het volgende diagram:
- Knooppunt 1 domineert knooppunt 2
- Knooppunt 2 domineert knooppunten 3, 4 en 6
- Knooppunt 3 domineert knooppunt 5
- Knooppunt 5 domineert knooppunt 8
- Knooppunt 6 domineert knooppunt 7
In het volgende voorbeeld zijn knooppunten #7
, #3
en GC dominators van #10
omdat ze in elk pad voorkomen, van de wortel (GC) tot #10
. Concreet is #7
de onmiddellijke dominator van #10
, omdat dit de dichtstbijzijnde dominator is op het pad van GC naar #10
.
V8-specificaties
Bij het profileren van het geheugen is het nuttig om te begrijpen waarom heap-snapshots er op een bepaalde manier uitzien. In deze sectie worden enkele geheugengerelateerde onderwerpen beschreven die specifiek betrekking hebben op de V8 JavaScript virtuele machine (V8 VM of VM).
JavaScript-objectrepresentatie
Er zijn drie primitieve typen:
- Getallen (bijvoorbeeld 3.14159..)
- Booleaanse waarden (waar of onwaar)
- Strijkers (bijv. 'Werner Heisenberg')
Ze kunnen niet naar andere waarden verwijzen en zijn altijd bladeren of afsluitende knooppunten.
Nummers kunnen worden opgeslagen als:
- een onmiddellijke 31-bit gehele getalwaarde, genaamd kleine gehele getallen ( SMI's ), of
- heap-objecten, ook wel heap-nummers genoemd. Heap-nummers worden gebruikt voor het opslaan van waarden die niet in het SMI-formulier passen, zoals doubles , of wanneer een waarde in een box moet worden geplaatst, zoals het instellen van eigenschappen erop.
Tekenreeksen kunnen worden opgeslagen in:
- de VM-heap , of
- extern in het geheugen van de renderer . Er wordt een wrapper-object gemaakt en gebruikt voor toegang tot externe opslag, waar bijvoorbeeld scriptbronnen en andere inhoud die van het web wordt ontvangen, worden opgeslagen in plaats van naar de VM-heap te worden gekopieerd.
Geheugen voor nieuwe JavaScript-objecten wordt toegewezen vanuit een speciale JavaScript-heap (of VM-heap ). Deze objecten worden beheerd door de garbage collector van V8 en zullen daarom in leven blijven zolang er minstens één sterke verwijzing naar hen is.
Native objecten zijn al het andere dat zich niet in de JavaScript-heap bevindt. Native object wordt, in tegenstelling tot het heap-object, gedurende zijn hele levensduur niet beheerd door de V8-garbagecollector en is alleen toegankelijk vanuit JavaScript met behulp van het JavaScript-wrapper-object.
Tegens string is een object dat bestaat uit paren strings die zijn opgeslagen en vervolgens zijn samengevoegd, en is het resultaat van aaneenschakeling. Het samenvoegen van de cons- stringinhoud vindt alleen plaats als dat nodig is. Een voorbeeld zou zijn wanneer een substring van een samengevoegde string moet worden geconstrueerd.
Als u bijvoorbeeld a en b samenvoegt, krijgt u een tekenreeks (a, b) die het resultaat van de aaneenschakeling vertegenwoordigt. Als je d later met dat resultaat aaneengeschakeld hebt, krijg je nog een cons-string ((a, b), d).
Arrays - Een array is een object met numerieke toetsen. Ze worden veelvuldig gebruikt in de V8 VM voor het opslaan van grote hoeveelheden gegevens. Sets van sleutelwaardeparen die als woordenboeken worden gebruikt, worden ondersteund door arrays.
Een typisch JavaScript-object kan een van de twee arraytypen zijn die worden gebruikt voor opslag:
- benoemde eigenschappen, en
- numerieke elementen
In gevallen waarin er een zeer klein aantal eigenschappen is, kunnen deze intern in het JavaScript-object zelf worden opgeslagen.
Kaart -een object dat het soort object en de indeling ervan beschrijft. Kaarten worden bijvoorbeeld gebruikt om impliciete objecthiërarchieën te beschrijven voor snelle toegang tot eigendommen .
Objectgroepen
Elke oorspronkelijke objectgroep bestaat uit objecten die onderlinge verwijzingen naar elkaar bevatten. Beschouw bijvoorbeeld een DOM-subboom waarin elk knooppunt een link heeft naar zijn ouder en linkt naar het volgende kind en de volgende broer of zus, waardoor een verbonden grafiek ontstaat. Houd er rekening mee dat native objecten niet in de JavaScript-heap worden weergegeven. Daarom hebben ze een grootte van nul. In plaats daarvan worden wrapperobjecten gemaakt.
Elk wrapper-object bevat een verwijzing naar het corresponderende oorspronkelijke object, zodat opdrachten ernaar kunnen worden omgeleid. Op zijn beurt bevat een objectgroep wrapperobjecten. Dit creëert echter geen oninbare cyclus, omdat GC slim genoeg is om objectgroepen vrij te geven waarvan de wrappers niet langer worden verwezen. Maar als u vergeet een enkele wrapper vrij te geven, houdt u de hele groep en de bijbehorende wrappers vast.