Rumah > Artikel > hujung hadapan web > Terokai peruntukan memori timbunan dalam Node dan bercakap tentang had ingatan!
Artikel ini akan membawa anda meneroka peruntukan memori timbunan dalam Node dan memperoleh pemahaman yang mendalam tentang had ingatan dalam Node.js, saya harap ia akan membantu anda.
Dalam artikel ini, saya akan meneroka peruntukan memori timbunan dalam Node, dan kemudian cuba meningkatkan memori kepada had yang boleh ditanggung oleh perkakasan. Kemudian kami akan mencari beberapa cara praktikal untuk memantau proses Node untuk menyahpepijat isu berkaitan memori.
OK, mari kita mulakan sebaik sahaja persiapan selesai!
Anda boleh tarik kod yang berkaitan di dalam gudang dan klon kod daripada GitHub saya:
https://github.com/beautifulcoder/node-memory-limitations
Pertama sekali, mari kita perkenalkan secara ringkas pengumpul sampah V8. Kaedah peruntukan storan ingatan adalah timbunan, yang dibahagikan kepada beberapa kawasan generasi. Apabila objek semakin tua semasa kitaran hayatnya, begitu juga dengan generasi yang dimilikinya.
Generasi terbahagi kepada generasi muda dan generasi tua, dan generasi muda juga dibahagikan kepada generasi baru dan generasi pertengahan. Memandangkan objek bertahan daripada pengumpulan sampah, mereka juga menyertai generasi lama.
Prinsip asas hipotesis generasi ialah kebanyakan subjek adalah muda. Pengumpul sampah V8 membina ini dengan hanya mempromosikan objek yang bertahan dalam pengumpulan sampah. Apabila objek disalin ke kawasan bersebelahan, objek itu akhirnya berakhir di generasi lama.
Penggunaan memori dalam Nodejs terutamanya dibahagikan kepada tiga aspek:
Memori timbunan ialah tumpuan utama kami hari ini. Sekarang setelah anda mengetahui lebih lanjut tentang pemungut sampah, tiba masanya untuk memperuntukkan beberapa ingatan pada timbunan!
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; }
Dalam tindanan panggilan, pembolehubah tempatan dimusnahkan apabila panggilan fungsi tamat. Jenis asas number
tidak pernah masuk ke dalam memori timbunan, tetapi diperuntukkan dalam timbunan panggilan. Tetapi objek arr akan masuk ke dalam timbunan dan mungkin bertahan dengan kutipan sampah.
Sekarang untuk ujian yang berani - tolak proses Nod ke had dan lihat di mana ia kehabisan memori timbunan:
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);
Dalam kod di atas, kita mulakan dengan 40 Selang milisaat memperuntukkan kira-kira 10 MB, memberikan masa yang cukup untuk pengumpulan sampah untuk mempromosikan objek yang masih hidup kepada generasi lama. process.memoryUsage
ialah alat untuk memulihkan metrik kasar tentang penggunaan timbunan. Apabila peruntukan timbunan bertambah, medan heapUsed merekodkan saiz timbunan. Medan ini merekodkan bilangan bait dalam RAM dan boleh ditukar kepada MB.
Hasil anda mungkin berbeza-beza. Pada komputer riba Windows 10 dengan 32GB RAM anda akan mendapat keputusan berikut:
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
Di sini, pemungut sampah akan cuba memampatkan memori sebagai pilihan terakhir sebelum menyerah dan membuang "Out of Heap" pengecualian. Proses ini mencapai had 4.1GB, dan mengambil masa 26.6 saat untuk menyedari bahawa perkhidmatan itu akan ditamatkan.
Beberapa sebab yang membawa kepada keputusan di atas masih tidak diketahui. Pengumpul sampah V8 pada asalnya berjalan dalam proses pelayar 32-bit dengan kekangan memori yang ketat. Keputusan ini menunjukkan bahawa had memori mungkin telah diwarisi daripada kod warisan.
Pada masa penulisan, kod di atas dijalankan di bawah versi Nod LTS terkini dan menggunakan boleh laku 64-bit. Secara teorinya, proses 64-bit sepatutnya boleh memperuntukkan lebih daripada 4GB ruang dan mudah berkembang kepada 16 TB ruang alamat.
node index.js --max-old-space-size=8000
Ini menetapkan had maksimum kepada 8GB. Berhati-hati apabila melakukan ini. Komputer riba saya mempunyai ruang 32GB. Saya mengesyorkan menetapkannya kepada jumlah ruang yang sebenarnya tersedia dalam RAM. Setelah memori fizikal habis, proses mula mengambil ruang cakera melalui memori maya. Jika anda menetapkan had terlalu tinggi, anda akan mendapat sebab baharu untuk menukar komputer Di sini kami cuba mengelakkan komputer daripada merokok~
Mari jalankan kod semula dengan had 8GB:
Heap 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 memoryKali ini saiz timbunan hampir 8GB, tetapi tidak cukup. Saya mengesyaki terdapat beberapa overhed dalam proses Node untuk memperuntukkan begitu banyak memori. Kali ini proses mengambil masa 45.7 saat untuk diselesaikan. Dalam persekitaran pengeluaran, ia mungkin mengambil masa tidak kurang daripada satu minit untuk kehabisan ingatan. Ini adalah salah satu sebab mengapa pemantauan dan mendapatkan pandangan tentang penggunaan memori adalah membantu. Penggunaan ingatan akan meningkat secara perlahan dari semasa ke semasa, dan mungkin mengambil masa beberapa hari sebelum anda tahu ada masalah. Jika proses anda terus ranap dan pengecualian "out of heap" muncul dalam log, mungkin terdapat kebocoran memori dalam kod anda. Proses ini juga mungkin mengambil lebih banyak memori kerana ia memproses lebih banyak data. Jika penggunaan sumber terus berkembang, mungkin sudah tiba masanya untuk memecahkan monolit ini kepada perkhidmatan mikro. Ini akan mengurangkan tekanan memori pada proses individu dan membolehkan nod berskala secara mendatar.
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 教程!!
Atas ialah kandungan terperinci Terokai peruntukan memori timbunan dalam Node dan bercakap tentang had ingatan!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!