Rumah  >  Artikel  >  hujung hadapan web  >  Nod belajar bagaimana untuk meminimumkan peruntukan timbunan dan mencegah kebocoran memori

Nod belajar bagaimana untuk meminimumkan peruntukan timbunan dan mencegah kebocoran memori

青灯夜游
青灯夜游ke hadapan
2023-01-11 20:25:392161semak imbas

Bagaimana untuk menyemak kebocoran memori dalam Node.js? Artikel berikut akan memperkenalkan anda kepada peruntukan timbunan Nodejs dan memperkenalkan cara untuk meminimumkan peruntukan timbunan dan mengelakkan kebocoran ingatan saya harap ia akan membantu anda.

Nod belajar bagaimana untuk meminimumkan peruntukan timbunan dan mencegah kebocoran memori

Isu pengurusan memori sentiasa menarik banyak perhatian dalam bidang komputer. Setiap perisian yang berjalan pada komputer anda diperuntukkan sebahagian kecil daripada memori terhad komputer. Memori ini mesti diurus dengan teliti dan diperuntukkan atau dikeluarkan pada masa yang sesuai.

Nodejs boleh mengendalikan tugas pengurusan memori yang membosankan melalui mekanisme pengumpulan sampah automatik yang cekap, membebaskan pembangun untuk terlibat dalam tugas lain. Walaupun Nodejs telah membantu pembangun menyelesaikan masalah pengurusan memori, masih sangat penting bagi pembangun untuk memahami mekanisme pengurusan memori dalam V8 dan Nodejs apabila menghadapi pembangunan aplikasi berskala besar.

Artikel ini terutamanya memperkenalkan cara memperuntukkan dan melepaskan memori dalam timbunan, dan membantu anda mengetahui cara meminimumkan peruntukan timbunan dan mencegah kebocoran memori. [Tutorial berkaitan yang disyorkan: tutorial video nodejs, Pengajaran pengaturcaraan]

Nodejs Peruntukan timbunan dalam

JavaScript dan Node.js abstrakkan banyak perkara untuk anda dan lakukan kebanyakan kerja berat di belakang tabir.

Kami tahu bahawa apabila sekeping kod dilaksanakan, pembolehubah dan objek dalam kod akan disimpan dalam memori tindanan atau ingatan timbunan, dan JavaScript kod akan disimpan dalam konteks pelaksanaan yang akan dilaksanakan .

ECMAScript Spesifikasi itu sendiri tidak menyatakan cara memori diperuntukkan dan diuruskan. Ini ialah butiran pelaksanaan yang bergantung pada JavaScript enjin dan seni bina sistem asas. Pemahaman mendalam tentang cara enjin mengendalikan pembolehubah adalah di luar skop artikel ini, tetapi jika anda ingin mengetahui lebih lanjut tentang cara V8 melakukan ini, sila rujuk artikel JavaScript内存模型揭秘 dan 数据是如何存储在V8 JS引擎内存中的?.

Mengapa penggunaan memori timbunan yang cekap penting dalam Node.js

Pembolehubah memori yang disimpan dalam timbunan akan sentiasa wujud melainkan ia dipadamkan oleh pengumpul sampah atau pelepasan . Memori timbunan ialah blok memori bersebelahan besar yang kekal dalam keadaan ini walaupun selepas ia diperuntukkan dan dikeluarkan.

Malangnya, disebabkan cara ingatan timbunan dikumpul dan dilepaskan, ingatan boleh dibazirkan, menyebabkan kebocoran.

menggunakan mekanisme pengumpulan sampah generasi, yang membahagikan objek kepada generasi yang berbeza (generasi muda dan generasi lama). Ruang penjanaan akan dibahagikan kepada kawasan yang berbeza - contohnya, generasi baru terdiri daripada ruang baru, dan generasi lama akan dibahagikan kepada ruang lama, ruang pemetaan dan ruang objek besar. Objek baharu pada mulanya diperuntukkan kepada ruang generasi baharu Apabila ruang generasi baharu habis, pemungut sampah akan melakukan mekanisme pembersihan untuk mengosongkan ruang. Objek yang bertahan dalam satu V8 larian disalin ke pertengahan generasi muda, dan objek yang bertahan dalam larian kedua dipindahkan ke generasi lama. GC

Memandangkan program yang sedang berjalan mengumpul memori terlebih dahulu, menduduki sumber memori maya yang berharga, apabila memori tidak lagi diperlukan, program mesti melepaskan memori, iaitu pelepasan memori.

Selain itu, jika ingatan dibebaskan (tidak kira di mana ia telah dibebaskan sebelum ini dalam timbunan), ingatan timbunan akan disatukan menjadi satu blok memori bersebelahan. Disebabkan oleh peningkatan kerumitan memori timbunan, penyimpanan di sini akan menanggung overhed prestasi yang lebih tinggi (tetapi membolehkan fleksibiliti yang lebih besar di kedai berikutnya).

Walaupun

mempunyai mekanisme pengumpulan sampah yang cekap, penggunaan memori timbunan yang tidak cekap boleh menyebabkan kebocoran memori. Aplikasi mungkin mengambil terlalu banyak memori atau malah ranap. Nodejs

Punca Nodejs Heap Memory Kebocoran

Pengumpul sampah mencari dan melepaskan ruang ingatan yatim, tetapi kadangkala ia mungkin tidak dapat menjejaki setiap cebisan memori. Ini boleh menyebabkan peningkatan beban yang tidak perlu, terutamanya untuk aplikasi yang besar. Kami akan membincangkan cara pemungut sampah dalam

berfungsi secara terperinci kemudian. Nodejs

Beberapa punca kebocoran ingatan yang paling biasa termasuk:

  • Berbilang rujukan
  • Pembolehubah global
  • Penutupan
  • Pemasa
  • Acara

Gunakan berbilang Ia adalah operasi yang sangat biasa untuk memegang rujukan kepada objek melalui penuding boleh ubah. Walaupun ini sangat mudah untuk anda, ia juga boleh menyebabkan kebocoran memori jika salah satu rujukan kepada objek dikumpul oleh pemungut sampah, tetapi rujukan lain tidak.

Dalam Node.js dan JavaScript aplikasi, pemasa dan fungsi panggil balik yang terlupa untuk dibersihkan juga merupakan dua punca biasa kebocoran memori. Objek yang terikat pada pemasa tidak akan dikumpul sampah sehingga tamat masa. Jika pemasa berjalan selama-lamanya, objek yang dirujuk tidak akan pernah dikumpul oleh pemungut sampah. Ini berlaku walaupun tiada penuding pembolehubah merujuk kepada objek, sekali gus mewujudkan kebocoran memori dalam timbunan.

Fikirkan tentang kod sampel:

const language = () => {
  console.log("Javascript");】
  // 递归自身
  setTimeout(() => language(), 1000);
}

Kod di atas akan sentiasa dijalankan dan tidak akan dikitar semula oleh pemungut sampah

Cara mengetahui Memori kebocoran dalam Nodejs

Terdapat beberapa alatan yang boleh digunakan untuk mengesan dan nyahpepijat kebocoran memori dalam Nodejs, termasuk proses dalam Chrome DevTools, Node. Papan pemuka pengumpul sampah untuk memoryUsage API dan AppSignal.

Menggunakan Chrome DevTools

Chrome DevTools mungkin merupakan salah satu alatan yang paling mudah. Untuk memulakan penyahpepijat, anda perlu memulakan inspect dalam mod Node. Jalankan node --inspect untuk melakukan ini.

Lebih khusus lagi, jika entri Node anda ialah app.js, anda perlu menjalankan node --inspect app.js untuk nyahpepijat aplikasi Node anda. Kemudian, buka pelayar Chromium dan pergi ke chrome://inspect. Anda juga boleh membuka halaman pemeriksa di Edge://inspect. Pada halaman Inspektor, anda sepatutnya melihat halaman seperti ini:

Perhatikan bahawa aplikasi Node yang anda cuba nyahpepijat muncul di bahagian bawah halaman Inspektor. Klik inspect untuk membuka penyahpepijat. Penyahpepijat mempunyai dua tab penting – Memory dan Profiler – tetapi dalam perbincangan ini, kita akan menumpukan pada tab Memory.

Menggunakan Chrome Penyahpepijat Cara paling mudah untuk mencari kebocoran memori ialah menggunakan 堆快照. Syot kilat boleh membantu anda menyemak beberapa pembolehubah atau menyemak saiz rizabnya.

Anda juga boleh mencari kebocoran memori dengan membandingkan berbilang syot kilat. Untuk idea yang baik, anda boleh menyimpan syot kilat sebelum dan selepas kebocoran memori dan membandingkan keduanya. Untuk mendapatkan syot kilat, anda boleh melakukannya dengan mengklik sekali pada Heap snapshot dan kemudian mengklik pada butang *Take snapshot. Ini mungkin mengambil sedikit masa, bergantung pada saiz timbunan Total JS aplikasi. Anda juga boleh memuatkan syot kilat sedia ada dengan mengklik butang DevTool di bahagian bawah. load

Apabila anda mempunyai dua atau lebih syot kilat, anda boleh membandingkan peruntukan timbunan dan mencari punca kebocoran memori dengan mudah. Anda boleh melihat syot kilat dengan cara berikut:

  • Summary: Objek dalam aplikasi anda dipaparkan dikumpulkan mengikut nama pembinaNode

  • Comparison: Menunjukkan perbezaan antara dua syot kilat

  • Containment: membolehkan anda Melihat ke dalam timbunan dan menganalisis objek yang dirujuk dalam ruang nama global

  • Statistics:

Terdapat dua lajur yang menonjol dalam

pemprofil timbunan - iaitu DevToolsShallow Size dan Retained Size.

Shallow Size mewakili saiz objek itu sendiri dalam ingatan. Saiz memori ini tidak besar untuk kebanyakan objek, kecuali jenis tatasusunan dan rentetan. Sebaliknya, Retained Size ialah saiz memori yang dikeluarkan apabila objek berkenaan dan objek bergantung dilepaskan atau menjadi tidak boleh diakses daripada nod akar.

bukan satu-satunya cara untuk mendapatkan petikan timbunan. Jika anda menggunakan Chrome DevTools 12.0 atau lebih tinggi, anda juga boleh menjalankan perintah nodejs: node --heapsnapshot-signal

node --heapsnapshot-signal=SIGUSR2 app.js
Walaupun mana-mana bendera boleh digunakan, adalah disyorkan untuk menggunakan isyarat yang ditentukan pengguna

atau SIGUSR1. SIGUSR2

Jika anda mengambil gambar pasangan daripada aplikasi yang dijalankan pada pelayan, anda boleh menggunakan fungsi

dalam pakej V8: writeHeapSnapshot

require("v8").writeHeapSnapshot();

这个方法要求 Nodejs 的版本高于 11.13。在早期的版本中,你可以使用相关的包来实现。

使用 Chrome DevTools 获取堆快照并不是调试内存问题的唯一方法。你也可以使用Allocation instrumentation on timeline 跟踪每个堆分配的情况。

内存分配时间轴显示了随时间变化的测量内存分配的情况。要启用此功能,需要先启动分析器(Profiler),然后运行应用程序示例以开始调试内存问题。如果你希望记录长时间运行的内存分配操作,并想要更小的性能开销,那么最好的选择是分配抽样方法。

通过 Nodeprocess.memoryUsage API

你也可以使用 Nodeprocess.memoryUsage API来观察内存使用情况。运行 process.memoryUsage,你可以访问以下内容:

  • rss:已分配的内存量
  • heapTotal:已分配堆的总大小
  • heapUsed:当执行进程时被使用内存总量
  • arrayBuffers:为 Buffer 实例分配的内存大小

使用 AppSignal 的垃圾收集器看板

为了可视化堆的变化情况,AppSignal 提供了一个方便的垃圾收集看板。当你将 Node.js 应用连接到AppSignal 时,这个看板会自动为你生成!

看看这个例子,在“V8 Heap Statistics”图表中,你可以清楚地看到内存使用的峰值:

如果看板中中的数据出现一个稳定增长的趋势,这意味着你的代码中或者依赖中存在内存泄漏的情况。

了解更多关于 Node.js 的AppSignal。

垃圾回收机制工作原理

如果你知道如何发现内存泄漏,但如何修复它们?我们可能很快就知道。但是首先重要的是理解 NodejsV8 是如何进行垃圾收集的。

垃圾回收机制会在不需要的时候释放内存。为了更高效的工作,垃圾回收算法必须正确的定义和识别不需要再内存中继续存储的内容。

在引用计数 GC 算法中,如果堆中的对象在堆栈中不再有引用,则该对象将被垃圾收集。该算法通过计数引用来工作——因此,如果引用计数为零,则对象将进行垃圾收集。尽管这个算法大多数时候都有效,但它在处理循环引用的情况时却失效了。

看一下代码示例:

let data = {};
data.el = data; 
let obj1 = {};
let obj2 = {};
obj1.a = obj2;
obj2.a = obj1;

具有循环引用的对象永远不会被清除作用域或被垃圾回收器回收,即使不再需要或使用它们。这会形成内存泄漏,并使应用程序效率低下。值得庆幸的是,Node.js 不再使用这种算法进行垃圾回收。

JavaScript 中的最上层对象是一个全局对象。在浏览器中,是 window 对象,但在 Nodejs 中,是 global 对象。该算法比引用计数算法更高效,并解决了循环引用的问题。

考虑到上面的例子,虽然 obj1 和 obj2 仍然存在循环引用,但如果它们不再从顶级对象可访问(不再需要),它们将被垃圾收集。

这种算法,通常称为 mark and sweep (标记清除算法)回收算法,非常有用。但是,你必须小心并显式地使一个对象从根节点不可访问,以确保它被垃圾收集。

修复 Nodejs App 中的内存泄漏

这有一些方法可以提高内存使用率并避免内存泄漏。

避免全局变量

全局变量包括使用 var 关键字声明的变量、this 关键字声明的变量和未使用关键字声明的变量。

我们已经偶然声明的全局变量(以及任何其他形式的全局变量)会导致内存泄漏。它们总是可以从全局对象访问,因此除非显式地设置为 null,否则不能被垃圾收集。

考虑下面的例子:

function variables() {
  this.a = "Variable one";  
  var b = "Variable two";
  c = "Variable three";
}

这三个变量都是全局变量。为了避免使用全局变量,可以考虑在文件顶部添加 use strict 指令来切换strict 模式。

使用 JSON.parse

JSON 的语法比 JavaScript 简单得多,因此它比 JavaScript 对象更容易解析。

事实上,如果你使用一个大型 JavaScript 对象,通过将其转化为字符串形式,使用时解析为 JSON,那么你可以在 V8Chrome 中将性能提高 1.7 倍。

在其他 JavaScript 引擎(如Safari)中,性能可能会更好。在 Webpack 中使用这种优化方法来提高前端应用程序的性能。

例如,不使用以下 JavaScript 对象:

const Person = { name: "Samuel", age: 25, language: "English" };

更有效的方法是将它们进行字符串化,然后将其解析为JSON

const Person = JSON.parse('{"name":"Samuel","age":25,"language":"English"}');

将大数据处理拆分为块并创建子进程

你获取在实际业务中会当处理大型数据时,遇到一些奇观的内存溢出的问题,例如大的 CSV 文件。当然,你可以通过扩展你的应用内存上限去处理任务,但是最好的方法是通过将大块数据分割为多个小块(chunks)。

在一些情况下,在多核机器上扩展 Node.js 应用程序可能会有所帮助。这涉及到将应用程序分离为主进程和工作进程。worker 处理繁重的逻辑,而 master 控制 worker 并在内存耗尽时重新启动它们。

有效使用计时器

我们创建的计时器可能会造成内存泄漏。为了提高堆内存管理,确保你的计时器不会永远运行。

特别是,使用 setInterval 创建计时器时,当不再需要计时器时调用 clearInterval 清除计时器是至关重要的。

当你不再需要使用 setTimeoutsetimmediation 创建计时器时,调用 clearTimeoutclearImmediate 也是一个很好的实践。

const timeout = setTimeout(() => {
  console.log("timeout");
}, 1500);
 
const immediate = setImmediate(() => {
  console.log("immediate");
});
 
const interval = setInterval(() => {
  console.log("interval");
}, 500);
 
clearTimeout(timeout);
clearImmediate(immediate);
clearInterval(interval);

移除闭包中不在需要的变量

JavaScript 中,闭包是一个常见概念。例如存在函数嵌套或者回调函数。如果在函数中使用了一个变量,当函数返回时,它将被标记为垃圾收集,但闭包可不是这样的。

代码示例:

const func = () => {
  let Person1 = { name: "Samuel", age: 25, language: "English" };
  let Person2 = { name: "Den", age: 23, language: "Dutch" };
 
  return () => Person2;
};

上面函数会一直引用父级作用域并将每个变量保存在作用域中。换句话说,虽然你仅仅使用了 Person2,但 Person1Person2 都被保存在作用域中。

这会消耗更多内存,并造成内存泄漏。为此,在面临上面这种情况时,你最好仅声明你需要的,将不需要的重置为 null

例如:

const func = () => {
  let Person1 = { name: "Samuel", age: 25, language: "English" };
  let Person2 = { name: "Den", age: 23, language: "Dutch" };
  Person1 = null;
  return () => Person2;
};

取消订阅观察者和 Event Emitters

具有较长生命周期的观察器和事件发射器可能是内存泄漏的来源,特别是如果你在不再需要它们时没有取消订阅的话。

代码示例:

const EventEmitter = require("events").EventEmitter;
const emitter = new EventEmitter();
 
const bigObject = {}; //Some big object
const listener = () => {
  doSomethingWith(bigObject);
};
emitter.on("event1", listener);

在这里,我们保留 bigObject 的内存,直到侦听器从发射器中释放,或者发射器被垃圾收集。为了解决这个问题,我们需要调用 removeEventListener 从发射器中释放监听器。

emitter.removeEventListener("event1", listener);

当连接到发射器的事件侦听器超过 10 个时,也可能发生内存泄漏。大多数情况下,你可以通过编写更高效的代码来解决这个问题。

但是,在某些情况下,你可能需要显式地设置最大事件侦听器。

例如:

emitter.setMaxListeners(n);

总结

在这篇文章中,我们探索了如何最小化你的堆和检测 Node.js 中的内存泄漏。

我们首先研究了 Node 中的堆分配,包括堆栈和堆的工作方式。然后,我们考虑了跟踪内存使用情况和内存泄漏的原因的重要性。

接下来,我们看到了如何使用 Chrome DevTools , Node 的进程来查找内存泄漏。memoryUsage API和 AppSignal 的垃圾收集可视化看板。

最后,我们发现了垃圾收集是如何工作的,并分享了一些修复应用程序内存泄漏的方法。

Seperti mana-mana bahasa pengaturcaraan lain, pengurusan memori adalah sangat penting dalam JavaScript dan Node.js. Saya harap pengenalan ini berguna kepada anda. Selamat mengekod!

Pautan asal: Minimumkan Peruntukan Timbunan dalam Node.js

Untuk lebih banyak pengetahuan berkaitan nod, sila Lawati: tutorial nodejs!

Atas ialah kandungan terperinci Nod belajar bagaimana untuk meminimumkan peruntukan timbunan dan mencegah kebocoran memori. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.cn. Jika ada pelanggaran, sila hubungi admin@php.cn Padam