In diesem Artikel erfahren Sie mehr über die Heap-Speicherzuweisung in Node und Sie erhalten ein detailliertes Verständnis der Speicherbeschränkungen in Node.js. Ich hoffe, dass er Ihnen weiterhilft!
In diesem Artikel werde ich die Heap-Speicherzuweisung in Node untersuchen und dann versuchen, den Speicher auf die Grenze zu erhöhen, die die Hardware ertragen kann. Dann werden wir einige praktische Möglichkeiten finden, die Prozesse von Node zu überwachen, um speicherbezogene Probleme zu debuggen.
OK, mach dich bereit und los!
Sie können den relevanten Code im Warehouse abrufen und den Code von meinem GitHub klonen:
https://github.com/beautifulcoder/node-memory-limitations
Zuerst , eine kurze Einführung Schauen Sie sich den V8-Garbage Collector an. Die Speicherzuweisungsmethode des Speichers ist Heap, der in mehrere Generationsbereiche unterteilt ist. Ein Objekt verändert im Laufe seines Lebenszyklus sein Alter und auch die Generation, zu der es gehört, ändert sich.
Die Generationen werden in die junge Generation und die alte Generation unterteilt, und die junge Generation wird auch in die neue Generation und die mittlere Generation unterteilt. Da Objekte die Garbage Collection überleben, schließen sie sich auch der alten Generation an.
Das Grundprinzip der Generationenhypothese ist, dass die meisten Probanden jung sind. Der V8-Garbage Collector baut darauf auf, indem er nur Objekte fördert, die eine Garbage Collection überleben. Wenn Objekte in angrenzende Regionen kopiert werden, landen sie schließlich in der alten Generation.
Der Speicherverbrauch in Nodejs ist hauptsächlich in drei Aspekte unterteilt:
Heap-Speicher ist heute unser Hauptaugenmerk. Nachdem Sie nun etwas mehr über den Garbage Collector wissen, ist es an der Zeit, etwas Speicher auf dem Heap zuzuweisen!
function allocateMemory(size) { // Simulate allocation of bytes const numbers = size / 8; const arr = []; arr.length = numbers; for (let i = 0; i < numbers; i++) { arr[i] = i; } return arr; }
Im Aufrufstapel werden lokale Variablen zerstört, wenn der Funktionsaufruf endet. Der zugrunde liegende Typ number
geht nie in den Heap-Speicher, sondern wird im Aufrufstapel zugewiesen. Das Objekt arr wird jedoch in den Heap verschoben und überlebt möglicherweise die Garbage Collection. number
永远不会进入堆内存,而是在调用栈中分配。但是对象arr将进入堆中并且可能在垃圾回收中幸存下来。
现在进行勇敢测试——将 Node 进程推到极限看看在哪个地方会耗尽堆内存:
const memoryLeakAllocations = []; const field = "heapUsed"; const allocationStep = 10000 * 1024; // 10MB const TIME_INTERVAL_IN_MSEC = 40; setInterval(() => { const allocation = allocateMemory(allocationStep); memoryLeakAllocations.push(allocation); const mu = process.memoryUsage(); // # bytes / KB / MB / GB const gbNow = mu[field] / 1024 / 1024 / 1024; const gbRounded = Math.round(gbNow * 100) / 100; console.log(`Heap allocated ${gbRounded} GB`); }, TIME_INTERVAL_IN_MSEC);
在上面的代码中,我们以 40 毫秒的间隔分配了大约 10 mb,为垃圾回收提供了足够的时间来将幸存的对象提升到老年代。process.memoryUsage
Nun für einen mutigen Test: Bringen Sie den Node-Prozess an seine Grenzen und sehen Sie, wo ihm der Heap-Speicher ausgeht:
Heap allocated 4 GB Heap allocated 4.01 GB <--- Last few GCs ---> [18820:000001A45B4680A0] 26146 ms: Mark-sweep (reduce) 4103.7 (4107.3) -> 4103.7 (4108.3) MB, 1196.5 / 0.0 ms (average mu = 0.112, current mu = 0.000) last resort GC in old space requested <--- JS stacktrace ---> FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Im obigen Code haben wir etwa 10 MB in 40-ms-Intervallen für die Speicherbereinigung zugewiesen. Für ausreichend Zeit ist gesorgt um erhaltene Objekte an die alte Generation weiterzugeben. process.memoryUsage
ist ein Tool zum Recyceln eines groben Indikators der Heap-Auslastung. Wenn die Heap-Zuweisung wächst, zeichnet das Feld heapUsed die Größe des Heaps auf. Dieses Feld zeichnet die Anzahl der Bytes im RAM auf und kann in MB umgewandelt werden.
Ihre Ergebnisse können variieren. Auf einem Windows 10-Laptop mit 32 GB RAM erhalten Sie das folgende Ergebnis:
node index.js --max-old-space-size=8000
Zum Zeitpunkt des Schreibens läuft der obige Code unter der neuesten LTS-Node-Version und verwendet eine ausführbare 64-Bit-Datei. Theoretisch sollte ein 64-Bit-Prozess in der Lage sein, mehr als 4 GB Speicherplatz zuzuweisen und problemlos auf 16 TB Adressraum zu wachsen.
Speicherzuteilungslimit erweiternHeap allocated 7.8 GB Heap allocated 7.81 GB <--- Last few GCs ---> [16976:000001ACB8FEB330] 45701 ms: Mark-sweep (reduce) 8000.2 (8005.3) -> 8000.2 (8006.3) MB, 1468.4 / 0.0 ms (average mu = 0.211, current mu = 0.000) last resort GC in old space requested <--- JS stacktrace ---> FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
Dadurch wird das maximale Limit auf 8 GB festgelegt. Seien Sie dabei vorsichtig. Mein Laptop verfügt über 32 GB Speicherplatz. Ich empfehle, den Wert auf den tatsächlich im RAM verfügbaren Speicherplatz einzustellen. Sobald der physische Speicher erschöpft ist, beginnen Prozesse, über den virtuellen Speicher Speicherplatz zu belegen. Wenn Sie das Limit zu hoch einstellen, erhalten Sie einen neuen Grund, den Computer zu wechseln. Hier versuchen wir, das Rauchen des Computers zu verhindern auf 8 GB, aber nicht ganz. Ich vermute, dass der Node-Prozess einen gewissen Mehraufwand für die Zuweisung von so viel Speicher verursacht. Diesmal dauerte der Vorgang 45,7 Sekunden.
In einer Produktionsumgebung darf es nicht weniger als eine Minute dauern, bis der gesamte Speicher aufgebraucht ist. Dies ist einer der Gründe, warum es hilfreich ist, den Speicherverbrauch zu überwachen und Einblicke zu gewinnen. Der Speicherverbrauch nimmt mit der Zeit langsam zu und es kann mehrere Tage dauern, bis Sie bemerken, dass ein Problem vorliegt. Wenn Ihr Prozess ständig abstürzt und in den Protokollen „Out of Heap“-Ausnahmen angezeigt werden, liegt möglicherweise ein Speicherverlust in Ihrem Code vor.
🎜Der Prozess beansprucht möglicherweise auch mehr Speicher, da mehr Daten verarbeitet werden. Wenn der Ressourcenverbrauch weiter steigt, ist es möglicherweise an der Zeit, diesen Monolithen in Microservices aufzuteilen. Dadurch wird die Speicherbelastung einzelner Prozesse verringert und eine horizontale Skalierung der Knoten ermöglicht. 🎜process.memoryUsage 的 heapUsed 字段还是有点用的,调试内存泄漏的一个方法是将内存指标放在另一个工具中以进行进一步处理。由于此实现并不复杂,因此主要解析下如何亲自实现。
const path = require("path"); const fs = require("fs"); const os = require("os"); const start = Date.now(); const LOG_FILE = path.join(__dirname, "memory-usage.csv"); fs.writeFile(LOG_FILE, "Time Alive (secs),Memory GB" + os.EOL, () => {}); // 请求-确认
为了避免将堆分配指标放在内存中,我们选择将结果写入 CSV 文件以方便数据消耗。这里使用了 writeFile 带有回调的异步函数。回调为空以写入文件并继续,无需任何进一步处理。 要获取渐进式内存指标,请将其添加到 console.log:
const elapsedTimeInSecs = (Date.now() - start) / 1000; const timeRounded = Math.round(elapsedTimeInSecs * 100) / 100; s.appendFile(LOG_FILE, timeRounded + "," + gbRounded + os.EOL, () => {}); // 请求-确认
上面这段代码可以用来调试内存泄漏的情况下,堆内存随着时间变化而增长。你可以使用一些分析工具来解析原生csv数据以实现一个比较漂亮的可视化。
如果你只是赶着看看数据的情况,直接用excel也可以,如下图:
在限制为4.1GB的情况下,你可以看到内存的使用率在短时间内呈线性增长。内存的消耗在持续的增长并没有变得平缓,这个说明了某个地方存在内存泄漏。在我们调试这类问题的时候,我们要寻找在分配在老世代结束时的那部分代码。
对象如果再在垃圾回收时幸存下来,就可能会一直存在,直到进程终止。
使用这段内存泄漏检测代码更具复用性的一种方法是将其包装在自己的时间间隔内(因为它不必存在于主循环中)。
setInterval(() => { const mu = process.memoryUsage(); // # bytes / KB / MB / GB const gbNow = mu[field] / 1024 / 1024 / 1024; const gbRounded = Math.round(gbNow * 100) / 100; const elapsedTimeInSecs = (Date.now() - start) / 1000; const timeRounded = Math.round(elapsedTimeInSecs * 100) / 100; fs.appendFile(LOG_FILE, timeRounded + "," + gbRounded + os.EOL, () => {}); // fire-and-forget }, TIME_INTERVAL_IN_MSEC);
要注意上面这些方法并不能直接在生产环境中使用,仅仅只是告诉你如何在本地环境调试内存泄漏。在实际实现时还包括了自动显示、警报和轮换日志,这样服务器才不会耗尽磁盘空间。
尽管上面的代码在生产环境中不可行,但我们已经看到了如何去调试内存泄漏。因此,作为替代方案,可以将 Node 进程包裹在 PM2 之类 的 守护进程 中。
当内存消耗达到限制时设置重启策略:
pm2 start index.js --max-memory-restart 8G
单位可以是 K(千字节)、M(兆字节)和 G(千兆字节)。进程重启大约需要 30 秒,因此通过负载均衡器配置多个节点以避免中断。
另一个漂亮的工具是跨平台的原生模块node-memwatch,它在检测到运行代码中的内存泄漏时触发一个事件。
const memwatch = require("memwatch"); memwatch.on("leak", function (info) { // event emitted console.log(info.reason); });复制代码
事件通过leak触发,并且它的回调对象中有一个reason会随着连续垃圾回收的堆增长而增长。
使用 AppSignal 的 Magic Dashboard 诊断内存限制
AppSignal 有一个神奇的仪表板,用于监控堆增长的垃圾收集统计信息。
上图显示请求在 14:25 左右停止了 7 分钟,允许垃圾回收以减少内存压力。当对象在旧的空间中停留太久并导致内存泄漏时,仪表板也会暴露出来。
在这篇文章中,我们首先了解了 V8 垃圾回收器的作用,然后再探讨堆内存是否存在限制以及如何扩展内存分配限制。
最后,我们使用了一些潜在的工具来密切关注 Node.js 中的内存泄漏。我们看到内存分配的监控可以通过使用一些粗略的工具方法来实现,比如memoryUsage一些调试方法。在这里,分析仍然是手动实现的。
另一种选择是使用 AppSignal 等专业工具,它提供监控、警报和漂亮的可视化来实时诊断内存问题。
希望你喜欢这篇关于内存限制和诊断内存泄漏的快速介绍。
更多node相关知识,请访问:nodejs 教程!!
Das obige ist der detaillierte Inhalt vonEntdecken Sie die Heap-Speicherzuweisung in Node und sprechen Sie über Speichergrenzen!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!