Kenntnisse in der Node.js-API können Sie schnell voranbringen, aber ein tiefgreifendes Verständnis des Speicherbedarfs von Node.js-Programmen kann Sie weiterbringen.
Beginnen wir damit, einen Blick auf unsere Speichernutzung mit „process.memoryUsage()“ zu werfen, die jede Sekunde aktualisiert wird:
setInterval(() => { console.log('Memory Usage:', process.memoryUsage()); }, 1000);
Da die Ausgabe in Bytes erfolgt, ist sie nicht benutzerfreundlich. Lassen Sie es uns aufpeppen, indem wir die Speichernutzung in MB:
formatieren
function formatMemoryUsageInMB(memUsage) { return { rss: convertToMB(memUsage.rss), heapTotal: convertToMB(memUsage.heapTotal), heapUsed: convertToMB(memUsage.heapUsed), external: convertToMB(memUsage.external) }; } const convertToMB = value => { return (value / 1024 / 1024).toFixed(2) + ' MB'; }; const logInterval = setInterval(() => { const memoryUsageMB = formatMemoryUsageInMB(process.memoryUsage()); console.log(`Memory Usage (MB):`, memoryUsageMB); }, 1000);
Jetzt können wir jede Sekunde die folgende Ausgabe erhalten:
Memory Usage (MB): { rss: '30.96 MB', // The actual OS memory used by the entire program, including code, data, shared libraries, etc. heapTotal: '6.13 MB', // The memory area occupied by JS objects, arrays, etc., dynamically allocated by Node.js // V8 divides the heap into young and old generations for different garbage collection strategies heapUsed: '5.17 MB', external: '0.39 MB' } Memory Usage (MB): { rss: '31.36 MB', heapTotal: '6.13 MB', heapUsed: '5.23 MB', external: '0.41 MB' }
Wir alle wissen, dass die Speichernutzung der V8-Engine begrenzt ist, nicht nur durch die Speicherverwaltungs- und Ressourcenzuweisungsrichtlinien des Betriebssystems, sondern auch durch seine eigenen Einstellungen.
Mit os.freemem() können wir sehen, wie viel freien Speicher das Betriebssystem hat, aber das bedeutet nicht, dass alles für ein Node.js-Programm zur Verfügung steht.
console.log('Free memory:', os.freemem());
Für 64-Bit-Systeme beträgt die standardmäßige maximale alte Speicherplatzgröße von Node.js V8 etwa 1,4 GB. Das bedeutet, dass V8 nicht automatisch mehr als dieses Limit verwendet, selbst wenn in Ihrem Betriebssystem mehr Speicher verfügbar ist.
Tipp: Dieses Limit kann durch das Festlegen von Umgebungsvariablen oder die Angabe von Parametern beim Starten von Node.js geändert werden. Wenn Sie beispielsweise möchten, dass V8 einen größeren Heap verwendet, können Sie die Option --max-old-space-size verwenden:
node --max-old-space-size=4096 your_script.js
Dieser Wert muss basierend auf Ihrer tatsächlichen Situation und Ihrem Szenario festgelegt werden. Wenn Sie beispielsweise eine Maschine mit viel Speicher haben, die eigenständig bereitgestellt wird, und Sie viele Maschinen mit kleinem Speicher verteilt bereitgestellt haben, wird sich die Einstellung für diesen Wert definitiv unterscheiden.
Lassen Sie uns einen Test durchführen, indem wir ein Array auf unbestimmte Zeit mit Daten füllen, bis der Speicher überläuft, und sehen, wann das passiert.
const array = []; while (true) { for (let i = 0; i < 100000; i++) { array.push(i); } const memoryUsageMB = formatMemoryUsageInMB(process.memoryUsage()); console.log(`Memory Usage (MB):`, memoryUsageMB); }
Das erhalten wir, wenn wir das Programm direkt ausführen. Nach längerem Hinzufügen von Daten stürzt das Programm ab.
Memory Usage (MB): { rss: '2283.64 MB', heapTotal: '2279.48 MB', heapUsed: '2248.73 MB', external: '0.40 MB' } Memory Usage (MB): { rss: '2283.64 MB', heapTotal: '2279.48 MB', heapUsed: '2248.74 MB', external: '0.40 MB' } # # Fatal error in , line 0 # Fatal JavaScript invalid size error 169220804 # # # #FailureMessage Object: 0x7ff7b0ef8070
Verwirrt? Liegt das Limit nicht bei 1,4G? Warum wird über 2G verwendet? Tatsächlich handelt es sich bei der 1,4-GB-Grenze von Node.js um eine historische Grenze der V8-Engine, die auf frühe V8-Versionen und bestimmte Konfigurationen anwendbar ist. In modernen Node.js und V8 passt Node.js seine Speichernutzung automatisch basierend auf den Systemressourcen an. In einigen Fällen kann es viel mehr als 1,4 GB verbrauchen, insbesondere wenn große Datenmengen verarbeitet oder speicherintensive Vorgänge ausgeführt werden.
Wenn wir das Speicherlimit auf 512 MB festlegen, läuft es über, wenn RSS etwa 996 MB erreicht.
Memory Usage (MB): { rss: '996.22 MB', heapTotal: '993.22 MB', heapUsed: '962.08 MB', external: '0.40 MB' } Memory Usage (MB): { rss: '996.23 MB', heapTotal: '993.22 MB', heapUsed: '962.09 MB', external: '0.40 MB' } <--- Last few GCs ---> [22540:0x7fd27684d000] 1680 ms: Mark-sweep 643.0 (674.4) -> 386.8 (419.4) MB, 172.2 / 0.0 ms (average mu = 0.708, current mu = 0.668) allocation failure; scavenge might not succeed [22540:0x7fd27684d000] 2448 ms: Mark-sweep 962.1 (993.2) -> 578.1 (610.7) MB, 240.7 / 0.0 ms (average mu = 0.695, current mu = 0.687) allocation failure; scavenge might not succeed <--- JS stacktrace ---> FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
Zusammenfassend lässt sich sagen, dass sich das Speicherlimit von Node.js genauer gesagt auf das Heap-Speicherlimit bezieht, das den maximalen Speicher darstellt, der von JS-Objekten, Arrays usw. belegt werden kann, die von V8 zugewiesen wurden.
Bestimmt die Größe des Heap-Speichers, wie viel Speicher ein Node.js-Prozess belegen kann? NEIN! Lesen Sie weiter.
Wir haben im Test gesehen, dass das Array nur etwas über 2GB fassen kann, bevor das Programm abstürzt. Wenn ich also eine 3-GB-Datei habe, kann ich sie dann nicht auf einmal im Node.js-Speicher ablegen?
Das können Sie!
Wir haben durch process.memoryUsage() einen externen Speicher gesehen, der vom Node.js-Prozess belegt, aber von V8 nicht zugewiesen wurde. Solange Sie die 3-GB-Datei dort ablegen, gibt es keine Speicherbeschränkung. Wie? Sie können Puffer verwenden. Buffer ist ein C-Erweiterungsmodul von Node.js, das Speicher mithilfe von C zuweist, nicht mit JS-Objekten und -Daten.
Hier ist eine Demo:
setInterval(() => { console.log('Memory Usage:', process.memoryUsage()); }, 1000);
Selbst wenn Sie 3 GB Speicher zuweisen, läuft unser Programm immer noch reibungslos und unser Node.js-Programm hat über 5 GB Speicher belegt, da dieser externe Speicher nicht durch Node.js, sondern durch die Beschränkung des zugewiesenen Speichers durch das Betriebssystem begrenzt ist zu Threads (Sie können also nicht einfach wild werden, auch der Puffer kann nicht mehr genügend Speicher haben; das Wesentliche ist, große Datenmengen mit Streams zu verarbeiten).
In Node.js ist der Lebenszyklus eines Buffer-Objekts an ein JavaScript-Objekt gebunden. Wenn der JavaScript-Verweis auf ein Buffer-Objekt entfernt wird, markiert der V8-Garbage Collector das Objekt als recycelbar, der zugrunde liegende Speicher des Buffer-Objekts wird jedoch nicht sofort freigegeben. Normalerweise wird dieser Teil des Speichers freigegeben, wenn der Destruktor der C-Erweiterung aufgerufen wird (z. B. während des Garbage Collection-Prozesses in Node.js). Dieser Vorgang ist jedoch möglicherweise nicht vollständig mit der Garbage Collection von V8 synchronisiert.
function formatMemoryUsageInMB(memUsage) { return { rss: convertToMB(memUsage.rss), heapTotal: convertToMB(memUsage.heapTotal), heapUsed: convertToMB(memUsage.heapUsed), external: convertToMB(memUsage.external) }; } const convertToMB = value => { return (value / 1024 / 1024).toFixed(2) + ' MB'; }; const logInterval = setInterval(() => { const memoryUsageMB = formatMemoryUsageInMB(process.memoryUsage()); console.log(`Memory Usage (MB):`, memoryUsageMB); }, 1000);
Zusammengefasst: Die Speichernutzung von Node.js besteht aus der JS-Heap-Speichernutzung (bestimmt durch die Garbage Collection von V8) und der Speicherzuweisung durch C
Die Generations-Garbage-Collection-Strategie ist in den Implementierungen moderner Programmiersprachen weit verbreitet! Ähnliche Strategien wie die Generational Garbage Collection finden sich in Ruby, .NET und Java. Wenn eine Speicherbereinigung stattfindet, führt dies oft zu einer „Stopp-die-Welt“-Situation, die sich unweigerlich auf die Programmleistung auswirkt. Dieses Design ist jedoch mit Blick auf die Leistungsoptimierung konzipiert.
Wenn Speicher zugewiesen wird, erfolgt dies innerhalb von From. Während der Garbage Collection werden die Live-Objekte in From überprüft und nach To kopiert, gefolgt von der Freigabe nicht Live-Objekte. In der anschließenden Sammlungsrunde werden die lebenden Objekte in „To“ in „From“ repliziert, woraufhin sich „To“ in „From“ verwandelt und umgekehrt. Bei jedem Speicherbereinigungszyklus werden Von und Bis vertauscht. Dieser Algorithmus repliziert während des Kopiervorgangs nur lebende Objekte und verhindert so die Entstehung von Speicherfragmenten.
Wie wird also die Lebendigkeit einer Variablen bestimmt? Die Erreichbarkeitsanalyse kommt ins Spiel. Betrachten Sie als Beispiel die folgenden Objekte:
Im Rahmen der Erreichbarkeitsanalyse:
Zugegebenermaßen kann die Referenzzählung als Hilfsmittel dienen. Dennoch ist es bei Vorhandensein von Zirkelbezügen nicht möglich, die wahre Lebendigkeit von Objekten genau zu ermitteln.
Im Speicher der alten Generation sind Objekte im Allgemeinen weniger aktiv. Wenn jedoch der Speicher der alten Generation voll ist, wird die Bereinigung des Speichers der alten Generation (Major GC) durch den Mark-Sweep-Algorithmus ausgelöst.
Der Mark-Sweep-Algorithmus besteht aus zwei Phasen: Markieren und Sweepen. In der Markierungsphase durchläuft die V8-Engine alle Objekte im Heap und markiert die lebenden Objekte. In der Kehrphase werden nur die nicht markierten Objekte geräumt. Der Vorteil dieses Algorithmus besteht darin, dass die Sweep-Phase relativ weniger Zeit in Anspruch nimmt, da der Anteil toter Objekte in der alten Generation relativ gering ist. Der Nachteil besteht jedoch darin, dass es nur löscht, ohne es zu komprimieren, was zu einem diskontinuierlichen Speicherplatz führen kann, was es unpraktisch macht, Speicher für große Objekte zuzuweisen.
Dieser Mangel führt zu einer Speicherfragmentierung, die den Einsatz eines anderen Algorithmus, Mark-Compact, erforderlich macht. Dieser Algorithmus verschiebt alle lebenden Objekte an ein Ende und löscht dann auf einen Schlag den ungültigen Speicherplatz auf der rechten Seite der Grenze, wodurch ein vollständiger und kontinuierlich verfügbarer Speicherplatz entsteht. Es behebt das Problem der Speicherfragmentierung, das möglicherweise durch den Mark-Sweep-Algorithmus verursacht wird, allerdings auf Kosten des höheren Zeitaufwands beim Verschieben einer großen Anzahl lebender Objekte.
Wenn Sie diesen Beitrag nützlich finden, geben Sie ihm bitte einen Daumen nach oben. :D
Das obige ist der detaillierte Inhalt vonWas genau ist das Speicherlimit von Node.js?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!