Speicherterminologie

Meggin Kearney
Meggin Kearney

In diesem Abschnitt werden allgemeine Begriffe beschrieben, die bei der Speicheranalyse verwendet werden, und gilt für eine Vielzahl von Tools zur Arbeitsspeicherprofilerstellung in verschiedenen Sprachen.

Die hier beschriebenen Begriffe und Konzepte beziehen sich auf den Heap Profiler der Chrome-Entwicklertools. Wenn Sie schon einmal mit Java, .NET oder einem anderen Arbeitsspeicher-Profiler gearbeitet haben, könnte dies eine Auffrischung sein.

Objektgrößen

Stellen Sie sich das Gedächtnis als eine Grafik mit primitiven Typen (z. B. Zahlen und Strings) und Objekten (assoziative Arrays) vor. Sie kann visuell als Diagramm mit einer Reihe miteinander verbundener Punkte dargestellt werden:

Visuelle Darstellung der Erinnerung

Ein Objekt kann Arbeitsspeicher auf zwei Arten enthalten:

  • Direkt durch das Objekt selbst.
  • Implizit, indem Verweise auf andere Objekte beibehalten und damit verhindert werden, dass diese Objekte automatisch von einer Speicherbereinigung (GC) automatisch entsorgt werden.

Wenn Sie den Heap-Profiler in den Entwicklertools verwenden (ein Tool zur Untersuchung von Speicherproblemen unter „Profile“), sehen Sie sich wahrscheinlich einige verschiedene Spalten mit Informationen an. Zwei davon heben sich ab: Flache Größe und Beibehaltene Größe. Aber was genau bedeuten diese?

Flache und beibehaltene Größe

Flache Größe

Dies ist die Größe des Arbeitsspeichers, der vom Objekt selbst zugewiesen wird.

Bei typischen JavaScript-Objekten ist ein Teil des Arbeitsspeichers für ihre Beschreibung und zum Speichern unmittelbarer Werte reserviert. In der Regel können nur Arrays und Strings eine signifikante flache Größe haben. Der Hauptspeicher von Strings und externen Arrays befindet sich jedoch häufig im Arbeitsspeicher des Renderers, sodass nur ein kleines Wrapper-Objekt auf dem JavaScript-Heap verfügbar gemacht wird.

Der Renderer-Arbeitsspeicher ist der gesamte Arbeitsspeicher des Prozesses, in dem eine geprüfte Seite gerendert wird: nativer Speicher + JS-Heap-Speicher der Seite + JS-Heap-Speicher aller dedizierten Worker, die von der Seite gestartet werden. Dennoch kann selbst ein kleines Objekt indirekt eine große Menge an Arbeitsspeicher enthalten, da verhindert wird, dass andere Objekte durch den automatischen Speicherbereinigungsprozess entsorgt werden.

Beibehaltene Größe

Dies ist die Größe des Arbeitsspeichers, der freigegeben wird, nachdem das Objekt selbst und seine abhängigen Objekte gelöscht wurden, die über GC-Roots nicht mehr erreichbar sind.

GC-Roots bestehen aus Handles, die lokal oder global erstellt werden, wenn von nativem Code auf ein JavaScript-Objekt außerhalb von V8 verwiesen wird. Alle diese Handles finden Sie in einem Heap-Snapshot unter GC-Roots > Handle-Bereich und GC-Roots > Globale Handles. Es kann verwirrend sein, die Aliasse in dieser Dokumentation zu beschreiben, ohne näher auf die Browserimplementierung eingehen zu müssen. Sowohl die GC-Roots als auch die Handles sind keine Sorge.

Es gibt viele interne GC-Wurzeln, von denen die meisten für die Nutzer nicht interessant sind. Aus Sicht der Anwendungen gibt es folgende Arten von Roots:

  • Globales Fensterobjekt (in jedem iFrame). In den Heap-Snapshots gibt es ein Distanzfeld, das die Anzahl der Attributverweise auf dem kürzesten Beibehaltungspfad aus dem Fenster angibt.
  • Dokument-DOM-Baum, der aus allen nativen DOM-Knoten besteht, die über das Dokument erreicht werden können Möglicherweise haben nicht alle JS-Wrapper, aber wenn sie die Wrapper haben, bleiben sie aktiv, während das Dokument aktiv ist.
  • Manchmal werden Objekte im Debugger-Kontext und in der Entwicklertools-Konsole beibehalten (z.B. nach der Konsolenauswertung). Erstellen Sie Heap-Snapshots mit einer klaren Konsole und keinen aktiven Haltepunkten im Debugger.

Das Arbeitsspeicherdiagramm beginnt mit einem Stamm. Dies kann das window-Objekt des Browsers oder das Global-Objekt eines Node.js-Moduls sein. Sie können nicht steuern, wie dieses Stammobjekt auf „GC“ gesetzt wird.

Stammobjekt kann nicht gesteuert werden

Alles, was vom Stammverzeichnis aus nicht erreichbar ist, erhält GC.

Aufbewahrungsstruktur für Objekte

Der Heap ist ein Netzwerk aus miteinander verbundenen Objekten. In der mathematischen Welt wird diese Struktur als Grafik oder Speichergrafik bezeichnet. Ein Graph wird aus Knoten konstruiert, die durch Kanten miteinander verbunden sind. Beide haben Labels.

  • Knoten (oder Objekte) werden mit dem Namen der constructor-Funktion gekennzeichnet, mit der sie erstellt wurden.
  • Edges werden mit den Namen der Attribute gekennzeichnet.

Profil mit Heap Profiler aufzeichnen In der Heap Profiler-Aufzeichnung unten sehen wir unter anderem die Entfernung, also die Entfernung zum GC-Stamm. Wenn sich fast alle Objekte desselben Typs in derselben Entfernung befinden, einige davon aber weiter entfernt sind, lohnt es sich, das zu untersuchen.

Abstand von der Wurzel

Dominatoren

Dominator-Objekte bestehen aus einer Baumstruktur, da jedes Objekt genau einen Dominator hat. Einem Dominator eines Objekts fehlen möglicherweise direkte Verweise auf ein Objekt, das es dominiert. Dies bedeutet, dass der Dominator eines Objekts kein Spannbaum im Diagramm ist.

Im folgenden Diagramm gilt:

  • Knoten 1 dominiert Knoten 2
  • Knoten 2 dominiert die Knoten 3, 4 und 6
  • Knoten 3 dominiert Knoten 5
  • Knoten 5 dominiert Knoten 8
  • Knoten 6 dominiert Knoten 7

Dominatorbaumstruktur

Im folgenden Beispiel ist der Knoten #3 der Dominator von #10, aber #7 ist auch in jedem einfachen Pfad von GC zu #10 vorhanden. Daher ist ein Objekt B ein Dominator von Objekt A, wenn B auf jedem einfachen Pfad von der Stammebene zu Objekt A vorhanden ist.

Animierter Dominator

V8 – Besonderheiten

Beim Erstellen von Arbeitsspeicherprofilen ist es hilfreich zu verstehen, warum Heap-Snapshots auf bestimmte Weise aussehen. In diesem Abschnitt werden einige arbeitsspeicherbezogene Themen beschrieben, die sich speziell auf die V8 JavaScript Virtual Machine (V8 VM oder VM) beziehen.

JavaScript-Objektdarstellung

Es gibt drei primitive Typen:

  • Zahlen (z.B. 3.14159...)
  • Boolesch (wahr oder falsch)
  • Strings (z.B. 'Werner Heisenberg')

Sie können nicht auf andere Werte verweisen und sind immer Blatt- oder Endknoten.

Zahlen können folgendermaßen gespeichert werden:

  • unmittelbare 31-Bit-Ganzzahlwerte, die als kleine Ganzzahlen (SMIs) bezeichnet werden, oder
  • Heap-Objekte, auch als Heap-Zahlen bezeichnet. Heap-Zahlen werden zum Speichern von Werten verwendet, die nicht in die SMI-Form passen (z. B. doubles), oder wenn ein Wert boxing sein muss, z. B. wenn Eigenschaften für ihn festgelegt werden.

Strings können folgendermaßen gespeichert werden:

  • den VM-Heap oder
  • extern im Speicher des Renderers gespeichert. Ein Wrapper-Objekt wird erstellt und für den Zugriff auf externen Speicher verwendet, in dem beispielsweise Skriptquellen und andere aus dem Web empfangene Inhalte gespeichert und nicht auf den VM-Heap kopiert werden.

Der Arbeitsspeicher für neue JavaScript-Objekte wird von einem dedizierten JavaScript-Heap (oder VM-Heap) zugewiesen. Diese Objekte werden vom Garbage Collector von V8 verwaltet und bleiben daher aktiv, solange mindestens ein starker Verweis darauf vorhanden ist.

Native Objekte sind alle anderen Objekte, die sich nicht im JavaScript-Heap befinden. Das native Objekt wird im Gegensatz zum Heap-Objekt während seiner gesamten Lebensdauer nicht vom V8-Garbage Collector verwaltet. Es kann nur über JavaScript mit seinem JavaScript-Wrapper-Objekt aufgerufen werden.

Ein Cons-String ist ein Objekt, das aus String-Paaren besteht, die gespeichert und dann verknüpft werden, und das das Ergebnis der Verkettung ist. Der Inhalt des cons string wird nur bei Bedarf zusammengeführt. Ein Beispiel wäre, wenn ein Teilstring eines verknüpften Strings erstellt werden muss.

Wenn Sie beispielsweise a und b verketten, erhalten Sie einen String (a, b), der das Ergebnis der Verkettung darstellt. Wenn Sie später d mit diesem Ergebnis verkettet haben, erhalten Sie einen weiteren Cons-String ((a, b), d).

Arrays: Ein Array ist ein Objekt mit numerischen Schlüsseln. Sie werden in der V8-VM zum Speichern großer Datenmengen in großem Umfang verwendet. Gruppen von Schlüssel/Wert-Paaren, die wie Wörterbücher verwendet werden, werden durch Arrays gesichert.

Ein typisches JavaScript-Objekt kann einer von zwei Arraytypen sein, die zum Speichern verwendet werden:

  • benannte Properties und
  • numerische Elemente

Bei sehr wenigen Eigenschaften können sie intern im JavaScript-Objekt selbst gespeichert werden.

Map: ein Objekt, das die Art des Objekts und sein Layout beschreibt. Beispielsweise werden Zuordnungen verwendet, um implizite Objekthierarchien für den schnellen Attributzugriff zu beschreiben.

Objektgruppen

Jede native Objektgruppe besteht aus Objekten, die gegenseitige Verweise enthalten. Betrachten Sie zum Beispiel eine DOM-Unterstruktur, in der jeder Knoten eine Verbindung zum übergeordneten Element hat und mit dem nächsten und dem nächsten gleichgeordneten Element verknüpft ist, wodurch ein verbundenes Diagramm gebildet wird. Beachten Sie, dass native Objekte nicht im JavaScript-Heap dargestellt werden und daher die Größe null haben. Stattdessen werden Wrapper-Objekte erstellt.

Jedes Wrapper-Objekt enthält einen Verweis auf das entsprechende native Objekt, an das Befehle weitergeleitet werden können. Eine Objektgruppe wiederum enthält Wrapper-Objekte. Dies führt jedoch nicht zu einem nicht erfassbaren Zyklus, da GC intelligent genug ist, um Objektgruppen freizugeben, auf deren Wrapper nicht mehr verwiesen wird. Wenn Sie jedoch vergessen, einen einzelnen Wrapper freizugeben, bleibt die gesamte Gruppe und die zugehörigen Wrapper erhalten.