Um JavaScript auszuführen, benötigt der Browser etwas Speicher, aber irgendwo müssen Sie Objekte, Grundelemente und Funktionen speichern, die für alle Benutzeraktionen erstellt wurden. Daher weist der Browser zuerst die erforderliche Menge an RAM zu, und wenn Objekte nicht verwendet werden, bereinigt er sie unabhängig.
Theoretisch klingt es gut. In der Praxis öffnet der Nutzer 20 Tabs von YouTube, sozialen Netzwerken, liest etwas, funktioniert, der Browser frisst Speicher, wie Hummer H2 - Benzin. Der Müllsammler läuft wie dieses Monster mit einem Mopp durch den gesamten Speicher und sorgt für Verwirrung, alles verlangsamt sich und stürzt ab.

Um zu verhindern, dass solche Situationen auftreten und die Leistung unserer Websites und Anwendungen nicht beeinträchtigt wird, sollte der Front-End-Entwickler wissen, wie sich Müll auf Anwendungen auswirkt, wie der Browser ihn sammelt und die Speicherbehandlung optimiert und wie sich alles von der harten Realität unterscheidet. Dies ist nur der Bericht von
Andrei Roenko ( Flapenguin ) auf der
Frontend Conf 2018 .
Wir verwenden den Garbage Collector (nicht zu Hause - in der Front-End-Entwicklung) jeden Tag, aber wir denken nicht wirklich darüber nach, was es überhaupt ist, was es uns kostet und welche Möglichkeiten und Einschränkungen es hat.
Wenn die Garbage Collection wirklich in JavaScript funktioniert, löschen sich die meisten npm-Module sofort nach der Installation.
Aber während dies nicht so ist, werden wir darüber sprechen, was ist - über das Zusammensetzen unnötiger Objekte.
Über den Sprecher :
Andrei Roenko hat die Yandex.Map-API entwickelt , ist seit sechs Jahren im Frontend und liebt es, seine eigenen hohen Abstraktionen zu erstellen und von Fremden zu Boden zu gehen.
Warum brauchen Sie Müllabfuhr?
Betrachten Sie das Beispiel von Yandex.Maps. Yandex.Maps ist ein riesiger und komplexer Dienst, der viel JS und fast alle vorhandenen Browser-APIs verwendet, mit Ausnahme von Multimedia-APIs. Die durchschnittliche Sitzungszeit beträgt 5-10 Minuten. Die Fülle an JavaScript erstellt viele Objekte. Durch Ziehen der Karte, Hinzufügen von Organisationen, Suchergebnissen und vielen anderen Ereignissen, die jede Sekunde auftreten, wird eine Lawine von Objekten erzeugt. Fügen Sie dieser Reaktion hinzu und Objekte werden noch mehr.
JS-Objekte belegen jedoch nur 30–40 MB auf der Karte. Für lange Yandex.Maps-Sitzungen und die ständige Zuweisung neuer Objekte reicht dies nicht aus.
Der Grund für das geringe Objektvolumen liegt darin, dass sie vom Garbage Collector erfolgreich erfasst und der Speicher wiederverwendet wird.
Heute werden wir über die Müllabfuhr von vier Seiten sprechen:
- Theorie Beginnen wir mit ihr, um dieselbe Sprache zu sprechen und uns zu verstehen.
- Harte Realität. Letztendlich führt der Computer Maschinencode aus, in dem nicht alle uns bekannten Abstraktionen vorhanden sind. Versuchen wir herauszufinden, wie die Speicherbereinigung auf niedriger Ebene funktioniert.
- Browser Realität. Lassen Sie uns sehen, wie die Speicherbereinigung in modernen Engines und Browsern implementiert ist und welche Schlussfolgerungen wir daraus ziehen können.
- Alltag - Lassen Sie uns über die praktische Anwendung des im Alltag gewonnenen Wissens sprechen.
Alle Aussagen sind mit Beispielen belegt, wie und wie man es nicht macht.
Warum das alles wissen?
Die Müllabfuhr ist für uns eine unsichtbare Sache. Wenn Sie jedoch wissen, wie sie angeordnet ist, werden Sie:
- Machen Sie sich ein Bild von dem von Ihnen verwendeten Tool, das für Ihre Arbeit nützlich ist.
- Verstehen Sie, wo bereits veröffentlichte Anwendungen optimiert werden können und wie Sie zukünftige Anwendungen so gestalten können, dass sie besser und schneller funktionieren.
- Wissen, wie man keine häufigen Fehler macht und keine Ressourcen mehr für nutzlose und schädliche „Optimierungen“ verschwendet.
Theorie
Joel Spolsky hat einmal gesagt:
Alle nicht trivialen Abstraktionen sind undicht.
Der Garbage Collector ist eine große, nicht triviale Abstraktion, die von allen Seiten gepatcht wird. Zum Glück fließt es sehr selten.
Beginnen wir mit einer Theorie, aber ohne langweilige Definitionen. Lassen Sie uns die Arbeit des Sammlers am Beispiel von einfachem Code analysieren:
window.Foo = class Foo { constructor() { this.x = { y: 'y' }; } work(name) { let z = 'z'; return function () { console.log(name, this.xy, z); this.x = null; }.bind(this); } };
- Der Code enthält eine Klasse .
- Die Klasse hat einen Konstruktor .
- Die Arbeitsmethode gibt eine verwandte Funktion zurück.
- Innerhalb der Funktion werden diese und einige Variablen aus dem Abschluss verwendet.
Mal sehen, wie sich dieser Code verhält, wenn wir ihn so ausführen:
var foo = new Foo();
Lassen Sie uns den Code und seine Komponenten genauer analysieren und mit der Klasse beginnen.
Klassenerklärung

Wir können davon ausgehen, dass Klassen in ECMAScript 2015 nur syntaktischer Zucker für Funktionen sind. Alle Funktionen haben:
- Funktion. [[Prototyp]] ist der eigentliche Prototyp der Funktion.
- Foo.prototype ist ein Prototyp für frisch erstellte Objekte.
- Foo.prototype hat über das Konstruktorfeld einen Link zurück zum Konstruktor. Dies ist ein Objekt, daher erbt es von Object.prototype .
- Die Arbeitsmethode ist eine separate Funktion, zu der es eine Verknüpfung gibt, ähnlich wie beim Konstruktor, da beide nur Funktionen sind. Er kann auch einen Prototyp festlegen und ihn als neu bezeichnen, aber selten verwendet jemand dieses Verhalten.
Prototypen nehmen auf der Rennstrecke viel Platz ein. Denken Sie also daran, dass dies der Fall ist, entfernen Sie sie jedoch der Einfachheit halber.
Erstellen eines Klassenobjekts

- Wir setzen unsere Klasse in ein Fenster, da Klassen standardmäßig nicht dort ankommen.
- Erstellen Sie ein Klassenobjekt.
- Durch das Erstellen eines Objekts wird der Prototyp des Klassenobjekts in Foo.prototype automatisch verfügbar gemacht. Wenn Sie versuchen, die Arbeitsmethode für ein Objekt aufzurufen, weiß es daher, um welche Art von Arbeit es sich handelt.
- Unser Konstruktor erstellt das Feld x im Objekt aus dem Objekt mit der Zeichenfolge.
Folgendes ist passiert:

Die Methode gibt eine gebundene Funktion zurück - dies ist ein solches spezielles "magisches" Objekt in JS, das aus einem gebundenen this und einer Funktion besteht, die aufgerufen werden muss. Die zugehörige Funktion hat auch einen Prototyp und einen anderen Prototyp, aber wir sind an der Schließung interessiert. Gemäß der Spezifikation wird der Verschluss in der Umgebung gespeichert. Höchstwahrscheinlich kennen Sie das Wort Scope besser, aber
in den Spezifikationen wird das Feld als Umgebung bezeichnet .

Die Umgebung speichert einen Verweis auf LexicalEnvironment. Dies ist ein komplexes Objekt, das komplizierter als auf einer Folie ist und Links zu allem speichert, auf das über eine Funktion zugegriffen werden kann. Zum Beispiel Fenster, Foo, Name und z. Es werden auch Links zu dem gespeichert, was Sie nicht explizit verwenden. Sie können beispielsweise eval verwenden und versehentlich nicht verwendete Objekte verwenden, JS sollte jedoch nicht beschädigt werden.
Also haben wir alle Objekte gebaut und jetzt werden wir alles zerstören.
Löschen Sie den Link zum Objekt
Beginnen wir mit dem Entfernen des Links zum Objekt. Dieser Link im Diagramm ist rot hervorgehoben.

Wir löschen und nichts passiert, weil vom
Fenster zum Objekt ein Pfad durch die
gebundene Funktionsfunktion führt.

Dies bringt uns zu einem typischen Fehler.
Häufiger Fehler - vergessenes Abonnement
externalElement.addEventListener('click', () => { if (this.shouldDoSomethingOnClick) { this.doSomething(); } })
Tritt auf, wenn Sie sich anmelden: Verwenden Sie
dies explizit über Bind- oder Pfeilfunktionen. Verwenden Sie etwas im Verschluss. Dann vergessen Sie, sich abzumelden, und die Lebensdauer Ihres Objekts oder der in der Schaltung befindlichen Daten entspricht der Lebensdauer eines Abonnements. Wenn dies beispielsweise ein DOM-Element ist, das Sie nicht berühren, ist dies höchstwahrscheinlich die Zeit bis zum Ende der Lebensdauer der Seite.
Um diese Probleme zu lösen:
- Abbestellen.
- Denken Sie über die Laufzeit des Abonnements nach und wem gehört es.
- Wenn Sie sich aus irgendeinem Grund nicht abmelden können, setzen Sie die Links auf Null (was auch immer = null) oder bereinigen Sie alle Felder des Objekts. Wenn Ihr Objekt undicht ist, ist es klein und es ist nicht schade.
- Verwenden Sie WeakMap. Vielleicht hilft dies in einigen Situationen.
Löschen Sie die Klassenreferenz
Fahren Sie fort und versuchen Sie, den durch die Klasse hervorgehobenen roten Link zu entfernen.

Wir löschen den Link und nichts ändert sich für uns. Der Grund dafür ist, dass auf die Klasse über BoundThis zugegriffen werden kann, in dem eine Verknüpfung zum Prototyp besteht, und dass im Prototyp eine Verknüpfung zum Konstruktor besteht.
Typischer Fehler nutzlose Arbeit
Warum werden all diese Demonstrationen benötigt? Weil das Problem eine Kehrseite hat, wenn Leute den Rat befolgen, Links zu wörtlich zu annullieren und alles im Allgemeinen zu annullieren.
destroy() { this._x = null; this._y = null;
Dies ist ein ziemlich wertloser Job. Wenn das Objekt nur aus Verweisen auf andere Objekte besteht und dort keine Ressourcen vorhanden sind, wird kein destroy () benötigt. Es reicht aus, den Bezug zum Objekt zu verlieren, und er wird von selbst sterben.
Es gibt keinen universellen Rat. Wenn es notwendig ist, annullieren Sie und wenn nicht, nicht annullieren. Nullstellen ist kein Fehler, sondern einfach nutzlose Arbeit.
Mach weiter. Rufen Sie die gebundene Funktionsmethode auf und sie entfernt den Link von [Objekt Foo] zu [Objekt Objekt]. Dies führt dazu, dass Objekte, die in einem blauen Rechteck auseinander liegen, im Diagramm angezeigt werden.

Diese Objekte sind JS-Müll. Er läuft großartig. Es gibt jedoch Müll, der nicht gesammelt werden kann.
Müll, der nicht geht
In vielen Browser-APIs können Sie ein Objekt erstellen und zerstören. Wenn das Objekt nicht zerstört wird, kann kein Sammler es zusammenbauen.
Objekte mit Funktionen zum Erstellen / Löschen von Paaren:
- createObjectURL (), revokeObjectURL ();
- WebGL: Programm / Shader / Puffer / Textur / etc erstellen / löschen;
- ImageBitmap.close ();
- indexDb.close ().
Wenn Sie beispielsweise vergessen, ObjectURL aus einem 200-MB-Video zu löschen, bleiben diese 200 MB bis zum Ende der Lebensdauer der Seite und sogar noch länger im Speicher, da zwischen den Registerkarten Daten ausgetauscht werden. Ähnlich in WebGL, indexDb und anderen Browser-APIs mit ähnlichen Ressourcen.
Glücklicherweise enthält das blaue Rechteck in unserem Beispiel nur JavaScript-Objekte. Dies ist also nur Müll, der entfernt werden kann.
Der nächste Schritt besteht darin, den letzten Link von links nach rechts zu löschen. Dies ist ein Verweis auf die Methode, die wir erhalten haben, eine verwandte Funktion.

Nach seiner Entfernung haben wir keine Links mehr links und rechts? Tatsächlich gibt es noch Links von der Schließung.

Es ist wichtig, dass es keine Links von links nach rechts gibt, daher ist alles außer Fenster Müll und es wird sterben.
Wichtiger Hinweis : Der Müll enthält Zirkelverweise, d. H. Objekte, die aufeinander verweisen. Das Vorhandensein solcher Links hat keine Auswirkungen, da der Garbage Collector nicht einzelne Objekte, sondern den gesamten Müll sammelt.

Wir haben uns die Beispiele angesehen und verstehen nun auf einer intuitiven Ebene, was Müll ist, aber lassen Sie uns eine vollständige Definition des Konzepts geben.
Müll ist alles, was kein lebendes Objekt ist.
Alles wurde sehr klar. Aber was ist ein lebendes Objekt?
Ein lebendes Objekt ist ein Objekt, das über Links vom Stammobjekt aus erreicht werden kann.Es erscheinen zwei neue Konzepte: "Links folgen" und "Stammobjekt". Ein Stammobjekt, das wir bereits kennen, ist window. Beginnen wir also mit den Links.
Was bedeutet es, den Links zu folgen?
Es gibt viele Objekte, die miteinander in Beziehung stehen und sich aufeinander beziehen. Wir werden ihnen entlang winken, beginnend mit dem Wurzelobjekt.
Wir initialisieren den ersten Schritt und fahren dann mit dem folgenden Algorithmus fort: Nehmen wir an, alles auf dem Wellenkamm sind lebende Objekte und sehen, worauf sie sich beziehen.

Wir initialisieren den ersten Schritt. Dann werden wir nach dem folgenden Algorithmus handeln: Nehmen wir an, dass alles Gelbe auf dem Wellenkamm lebende Objekte sind, und sehen wir, worauf sie sich beziehen.
Worauf sie sich beziehen, werden wir einen neuen Wellenkamm machen:

Fertig und von vorne anfangen:
- Wir beleben wieder.
- Wir schauen uns an, worauf sie sich beziehen.
- Erstellen Sie einen neuen Wellenberg, animieren Sie Objekte.
- Wir schauen uns an, worauf sie sich beziehen.

Wenn wir bemerken, dass ein Pfeil auf ein bereits lebendes Objekt zeigt, tun wir einfach nichts. Weiter nach dem Algorithmus, bis die zu umlaufenden Objekte erschöpft sind. Dann sagen wir, dass wir alle lebenden Objekte gefunden haben und alles andere Müll ist.

Dieser Vorgang wird als
Markierung bezeichnet .
Was bedeutet das Stammobjekt?
- Fenster
- Fast alle Browser-APIs.
- Alles Versprechen.
- Alles, was in Microtask und Macrotask enthalten ist.
- Mutationsbeobachter, RAF, Idle-Callbacks. Alles, was von dem, was in der RAF liegt, erreicht werden kann, kann nicht gelöscht werden. Wenn Sie das in der RAF verwendete Objekt löschen, wird wahrscheinlich etwas schief gehen.
Die Montage kann jederzeit erfolgen. Jedes Mal, wenn geschweifte Klammern oder Funktionen angezeigt werden, wird ein neues Objekt erstellt. Möglicherweise ist nicht genügend Speicher vorhanden, und der Sammler sucht kostenlos nach:
function foo (a, b, c) { function bar (x, y, z) { const x = {};
In diesem Fall sind die Stammobjekte alles auf dem Aufrufstapel. Wenn Sie beispielsweise an der Zeile mit X anhalten und löschen, worauf sich Y bezieht, stürzt Ihre Anwendung ab. JS erlaubt uns solche Frivolitäten nicht, daher können Sie kein Objekt aus Y löschen.
Wenn der vorherige Teil kompliziert schien, wird es noch schwieriger.
Harte Realität
Lassen Sie uns über die Welt der Maschinen sprechen, in der wir uns mit Eisen und physischen Medien beschäftigen.
Speicher ist ein großes Array, in dem nur Zahlen liegen, zum Beispiel: neues Uint32Array (16 * 2 ** 30).
Lassen Sie uns Objekte im Speicher erstellen und von links nach rechts hinzufügen. Wir schaffen eine, zweite, dritte - sie haben alle unterschiedliche Größen. Wir setzen Links auf den Weg.

Beim siebten Objekt ist der Platz beendet, weil wir 2 freie Felder haben, aber wir brauchen 5.
Was kann man hier machen? Die erste Option ist ein Absturz. Auf dem Hof im Jahr 2018 hat jeder die neuesten MacBooks und 16 GB RAM. Es gibt keine Situationen, in denen es keine Erinnerung gibt!
Es ist jedoch eine schlechte Idee, die Dinge laufen zu lassen, da dies im Web zu einem ähnlichen Bildschirm führt:

Dies ist nicht das Verhalten, das wir vom Programm erwarten, aber im Allgemeinen ist es gültig. Es gibt eine Kategorie von Sammlern namens
No-op .
No-Op-Sammler
Vorteile:
- Der Sammler ist sehr einfach.
- Es gibt einfach keine Speicherbereinigung.
- Sie müssen nicht schreiben oder über das Gedächtnis nachdenken.
Nachteile:
- Alles fällt so, dass es nie wieder steigt.
Für das Frontend ist der No-Op-Collector irrelevant, wird aber im Backend verwendet. Wenn sich beispielsweise mehrere Server hinter den Balancern befinden, erhält die Anwendung 32 GB RAM und wird dann vollständig beendet. Es ist einfacher und die Leistung wird nur durch einen einfachen Neustart verbessert, wenn der Speicher knapp wird.
Im Web ist das unmöglich und Sie müssen es bereinigen.
Müll suchen und löschen
Wir fangen an mit Müll zu putzen. Wir wissen bereits, wie es geht. Müll - Objekte C und F im vorherigen Schema, da Sie sie nicht über die Pfeile vom Stammobjekt aus erreichen können.
Wir nehmen diesen Müll, füttern ihn dem Müllliebhaber und Sie sind fertig.

Nach der Reinigung ist das Problem nicht gelöst, da Löcher im Speicher verbleiben. Bitte beachten Sie, dass es 7 freie Felder gibt, aber 5 davon können wir immer noch nicht zuweisen. Es trat eine Fragmentierung auf und die Montage war beendet. Ein solcher Algorithmus mit Löchern heißt
Mark and Sweep .
Markieren und fegen
Vorteile:
- Ein sehr einfacher Algorithmus. Eine der ersten, die Sie kennenlernen werden, wenn Sie etwas über Garbage Collector lernen.
- Es funktioniert proportional zur Müllmenge, kommt aber nur zurecht, wenn wenig Müll vorhanden ist.
- Wenn Sie nur lebende Objekte haben, verschwendet er keine Zeit und tut einfach nichts.
Nachteile:
- Es erfordert eine komplexe Logik, um nach freiem Speicherplatz zu suchen, denn wenn der Speicher viele Lücken aufweist, müssen Sie in jedem Objekt ein Objekt anprobieren, um zu verstehen, ob es passt oder nicht.
- Fragmente Erinnerung. Es kann vorkommen, dass bei freien 200 MB der Speicher in kleine Teile aufgeteilt wird und wie im obigen Beispiel kein fester Speicher für das Objekt vorhanden ist.
Wir suchen nach anderen Ideen. Wenn Sie sich das Bild ansehen und nachdenken, ist der erste Gedanke, alles nach links zu verschieben. Dann gibt es rechts ein großes und freies Stück, in das unser Objekt ruhig passt.
Es gibt einen solchen Algorithmus, der als
Mark and Compact bezeichnet wird .
Markieren und kompakt
Vorteile:
- Speicher defragmentieren.
- Es arbeitet proportional zur Anzahl der lebenden Objekte, was bedeutet, dass es verwendet werden kann, wenn praktisch keine Ablagerungen vorhanden sind.
Nachteile:
- Schwierig in Arbeit und Umsetzung.
- Verschiebt Objekte. Wir haben das Objekt verschoben, kopiert, jetzt befindet es sich an einem anderen Ort und der gesamte Vorgang ist ziemlich teuer.
- Je nach Implementierung sind 2-3 Durchgänge im gesamten Speicher erforderlich - der Algorithmus ist langsam.
Hier kommen wir zu einer anderen Idee.
Die Speicherbereinigung ist nicht kostenlos
In den Hochleistungs-APIs wie WebGL, WebAudio und WebGPU, die sich noch in der Entwicklung befinden, werden Objekte in separaten Phasen erstellt und gelöscht. Diese Spezifikationen sind so geschrieben, dass die Speicherbereinigung nicht durchgeführt wird. Außerdem gibt es dort nicht einmal Promise, sondern pull () - Sie fragen einfach jeden Frame: "Ist etwas passiert oder nicht?".
Semispace aka Lisp 2
Es gibt noch einen anderen Sammler, über den ich sprechen möchte. Was ist, wenn Sie keinen Speicher freigeben, sondern alle lebenden Objekte an einen anderen Ort kopieren?
Versuchen wir, das Stammobjekt "wie es ist" zu kopieren, das irgendwo verweist.

Und dann alle anderen.

Es gibt keine Ablagerungen oder Löcher im Speicher oben. Alles scheint in Ordnung zu sein, aber es treten zwei Probleme auf:
- Doppelte Objekte - wir haben zwei grüne und zwei blaue Objekte. Welches verwenden?
- Verknüpfungen von neuen Objekten führen zu alten Objekten und nicht zueinander.
Mit Links wird alles mit Hilfe einer speziellen algorithmischen „Magie“ gelöst, und wir können mit der Duplizierung von Objekten fertig werden, indem wir alles unten löschen.

Infolgedessen haben wir freien Speicherplatz und nur lebende Objekte in der oben angegebenen normalen Reihenfolge. Dieser Algorithmus wird als
Semispace ,
Lisp 2 oder einfach als "
Kopiersammler " bezeichnet.
Vorteile:
- Speicher defragmentieren.
- Einfach.
- Kann mit einer Bypass-Phase kombiniert werden.
- Es funktioniert proportional zur Anzahl der lebenden Objekte im Laufe der Zeit.
- Funktioniert gut, wenn es viel Müll gibt. Wenn Sie 2 GB Speicher und 3 Objekte haben, werden Sie nur 3 Objekte umgehen und die restlichen 2 GB scheinen weg zu sein.
Nachteile:
- Doppelter Speicherverbrauch. Sie verwenden 2 Mal mehr Speicher als nötig.
- Das Bewegen von Objekten ist ebenfalls keine sehr billige Operation.
Hinweis: Garbage Collectors können Objekte verschieben.
Im Web ist dies irrelevant, auf Node.js sogar sehr. Wenn Sie die Erweiterung in C ++ schreiben, weiß die Sprache nichts darüber. Daher gibt es dort doppelte Links, die als handle bezeichnet werden und ungefähr so aussehen: v8 :: Local <v8 :: String>.
Wenn Sie Plugins für Node.js schreiben, sind die Informationen daher hilfreich.
Wir fassen die verschiedenen Algorithmen mit ihren Vor- und Nachteilen in der Tabelle zusammen. Es hat auch einen Eden-Algorithmus, aber darüber später.

Ich möchte wirklich einen Algorithmus ohne Nachteile, aber das ist nicht. Deshalb nehmen wir das Beste aus allen Welten: Wir verwenden mehrere Algorithmen gleichzeitig. In einem Speicher sammeln wir Müll mit einem Algorithmus und in einem anderen mit einem anderen Algorithmus.
Wie kann man die Wirksamkeit des Algorithmus in einer solchen Situation verstehen?
Wir können das Wissen kluger Ehemänner aus den 60er Jahren nutzen, die sich alle Programme angesehen und erkannt haben:
Schwache Generationshypothese: Die meisten Objekte sterben jung.
Diese wollten sie sagen, dass alle Programme nur Müll produzieren. In dem Versuch, Wissen zu nutzen, werden wir zu dem kommen, was als „Versammlung nach Generationen“ bezeichnet wird.
Generationsversammlung
Wir schaffen zwei Erinnerungsstücke, die in keiner Weise miteinander verbunden sind: links Eden und rechts Mark and Sweep. In Eden schaffen wir Objekte. Viele Objekte.

Wenn Eden sagt, dass es voll ist, beginnen wir mit der Müllabfuhr. Wir finden lebende Objekte und kopieren sie an einen anderen Sammler.

Eden selbst ist vollständig gereinigt und wir können weitere Objekte hinzufügen.

Unter Berufung auf die Hypothese von Generationen haben wir entschieden, dass die Objekte c, g, i höchstwahrscheinlich lange leben werden, und Sie können sie seltener auf Müll überprüfen. Wenn Sie diese Hypothese kennen, können Sie Programme schreiben, die den Sammler täuschen. Dies kann getan werden, aber ich rate Ihnen nicht, da dies fast immer zu unerwünschten Wirkungen führen wird. Wenn Sie langlebigen Müll erstellen, beginnt der Sammler zu glauben, dass er nicht gesammelt werden muss.
Ein klassisches Beispiel für Betrug ist der LRU-Cache. Ein Objekt liegt lange im Cache, der Sammler betrachtet es und glaubt, dass es es noch nicht sammeln wird, da das Objekt sehr lange leben wird. Dann gelangt ein neues Objekt in den Cache, und ein großes altes wird herausgeschoben, und es ist nicht mehr möglich, dieses große Objekt sofort zusammenzusetzen.
Wie man jetzt sammelt, wissen wir. Sprechen Sie darüber, wann Sie abholen müssen.
Wann sammeln?
Die einfachste Option ist, wenn wir
einfach alles stoppen , den Build starten und dann die JS-Arbeit erneut starten.

In modernen Computern mehr als ein Ausführungsthread. Im Web ist dies den Web Workern bekannt. Warum nicht
den Montageprozess übernehmen und
parallelisieren? Das gleichzeitige Ausführen mehrerer kleiner Operationen ist schneller als eine große.

Eine andere Idee ist es, sorgfältig eine Momentaufnahme des aktuellen Status zu
erstellen und parallel zu JS zu erstellen .

Wenn Sie dies interessiert, empfehle ich Ihnen zu lesen:
- Das einzige und wichtigste Montagebuch, Garbage Collection Handbook.
- Wikipedia als universelle Ressource.
- Website memorymanagement.org.
- Berichte und Artikel von Alexander Shepelev . Er spricht über Java, aber in Bezug auf Müll funktionieren Java und V8 ungefähr gleich.
Browser Realität
Kommen wir dazu, wie Browser alles verwenden, worüber wir gesprochen haben.
IoT-Engines
Beginnen wir nicht mit Browsern, sondern mit Internet of Things-Engines: JerryScript und Duktape. Sie verwenden Mark'n'sweep und Stop the World-Algorithmen.
IoT-Engines arbeiten mit Mikrocontrollern, was bedeutet: Die Sprache ist langsam; zweite hängt; Fragmentierung und das alles für eine teekanne mit beleuchtung :)
Wenn Sie Internet of Things in JavaScript schreiben, sagen Sie es uns in den Kommentaren? Gibt es irgendeinen Punkt?
Wir lassen IoT-Engines in Ruhe und interessieren uns für:
- V8.
- SpiderMonkey Tatsächlich hat er kein Logo. Selbstgemachtes Logo :)
- Von WebKit verwendeter JavaScriptCore.
- ChakraCore, der in Edge verwendet wird.

Alle Motoren sind ungefähr gleich, daher werden wir über V8 als den bekanntesten sprechen.
V8
- Fast alles serverseitige JavaScript, weil es Node.js ist.
- Fast 80% des clientseitigen JavaScript.
- Die geselligsten Entwickler, es gibt viele Informationen und gute Quellcodes, die am einfachsten zu lesen sind.
Der V8 verwendet Generationsassemblierung.

Der einzige Unterschied ist, dass wir früher zwei Sammler hatten und jetzt drei:
- In Eden wird ein Objekt erstellt.
- Irgendwann in Eden gibt es zu viel Müll und das Objekt wird in Semispace übertragen.
- Das Objekt ist jung und wenn der Sammler feststellt, dass es zu alt und langweilig ist, wirft es es in Mark and Sweep, in dem die Müllabfuhr äußerst selten ist.
Sie können deutlich sehen, wie es auf der
Speicherspur aussieht.

Es fallen mehrere große Wellen mit kleinen Wellen auf. Kleine sind kleine Baugruppen, und große sind große.
Der Sinn unserer Existenz besteht nach der Generationshypothese darin, Müll zu erzeugen. Der nächste Fehler ist also die Angst, Müll zu erzeugen.
Papierkorb kann erstellt werden, wenn es sich wirklich um Papierkorb handelt. , , , .
mark
V8 .

Stop the world, , JS, .
?
1 3%, .
3% = 1/33 GameDev. GameDev 3% 1 , . GameDev .
const pool = [new Bullet(), new Bullet(), ]; function getFromPool() { const bullet = pool.find(x => !x.inUse); bullet.isUse = true; return bullet; } function returnToPool(bullet) { bullet.inUse = false; }
, , 10 000 .
— . , . , .
: Chromium
, , , Chromium.
> performance.memory MemoryInfo { totalJSHeapSize: 10000000, usedJSHeapSize: 10000000, jsHeapSizeLimit: 2330000000 }
Chromium
performance.memory , , Chromium .
: Chromium 2 JavaScript.
, .
: Node
Node.js
process.memoryUsage , .
> process.memoryUsage() { rss: 22839296, heapTotal: 10207232, heapUsed: 5967968, external: 12829 }
, - , . . .
Die Zukunft
— , .
proposal , .
Node.js, c
node-weak , , .
let cached = new WeakRef(myJson);
, , - JS. , , , .
WebAssembly , . , , , .
: v8.dev JS.
?
Tagesablauf
DevTools :
Performance Memory . Chromium, , Firefox Safari .
Performance
Trace, «Memory» Performance, JS .

JS V8 , . . , GC 30 1200 JS, 1/40.
Memory
.

.

, . , , , V8 , . , .
, , Q ( compiled code) — React . , ?
, , , .
, .

, , , . , — 4 . , .

React, - : . , JSX.
Performance Memory , :
- Chromium: about:tracing.
- Firefox: about:memory about:performance, .
- Node — trace-gc, —expose-gc, require('trace_events'). trace_events .
Zusammenfassung
- , , , .
- .
- . , ?
- , - .
- SPA, , 1 , .
- , - .
:
flapenguin.me ,
Twitter ,
GitHub .
- ++ . YouTube-
.
, 2018 , . Frontend Conf 2018.
, :)