NodeJs的事件循环、垃圾回收、调试

Nodejs事件循环

与浏览器的事件循环不同,Node.js 的事件循环构建在 libuv 库之上,专为 I/O 密集型操作优化。

node启动后,先执行完所有的同步代码,再执行队列中所有的process.nextTick 和微任务,最后进入事件循环。

  1. Timers:执行 setTimeout() 和 setInterval() 的回调。
    注意,其中每执行完一个立刻执行队列中所有的process.nextTick 和微任务,再执行下一个Timer回调。
  2. IO callbacks:处理一些系统级的 I/O 回调,如TCP错误。
  3. Idle, Prepare:Node内部使用,不常见。
  4. Poll:检索新的 I/O 事件,执行与 I/O 相关的回调。(新请求、异步文件读取等)
    在此期间如果没有其他阶段的任务需要执行,那么将一直处于 poll 阶段,以保证对IO事件的尽快响应。
  5. Check:执行 setImmediate() 回调。
  6. Close Callbacks:处理关闭的回调,如 socket.on('close', ...)

在每两个阶段之间都会依次执行process.nextTick 和 微任务(Promise回调、await回调)队列。
注意,同一阶段中,process.nextTick 总是优先于其他微任务执行。

定时器

NodeJS中的定时器是由timers模块实现的,该模块是全局的,因此无需被require,但其原理与浏览器端不同,是基于Node.js 事件循环构建。

  • setTimeoutsetIntervalsetImmediate定时器函数返回值在浏览器端是一个定时器id数字,但在Node端是当前定时器的实例对象,同时其中函数的this指向也指向该定时器实例对象(箭头函数正常指向global)。
  • 定时器函数实例额外具有unref()ref()方法。
const timer = setTimeout(function(){console.log(this)}, 100);//Timeout
setTimeout(()=>console.log(this==global));//true
timer.unref(); // 事件循环在只有这个定时器时退出
timer.ref();   // 恢复默认行为
  • 注意:Node中setTimeout有最小间隔1ms,因此根据主代码中同步任务执行情况可能导致setTimeoutsetImmediate先执行(例如耗时不到1ms,直接进入了Timers阶段)。
    因此通常将其放到Timers之后的阶段中执行,即可保证setImmediate先触发。
const fs = require('fs');
const path = require('path');

// 在IO结束的callback执行 也就是 poll 阶段会执行该 fs.readFile callback
// IO回调中存在 setImmediate 那么EventLoop的下一个阶段一定会进入check阶段
// 进而一定会优先执行 setImmediate 的回调
fs.readFile(path.resolve(__dirname, 'package.json'), (err) => {
  if (err) {
    console.log(err, 'err');
  }

  setTimeout(() => {
    console.log('timer');
  });

  setImmediate(() => {
    console.log('immediate');
  });
});
  • setImmediate可用在当前I/O 操作完成后立即执行某任务,或用于拆分复杂任务,避免阻塞事件循环。
    比起setTimeout(fn,0)开销更小,比起process.nextTick不容易饿死IO(阻塞事件循环)。
function heavyComputation(data) {
  if (data.length === 0) return;
  // 处理一部分
  processChunk(data.splice(0, 100));
  // 延迟下一轮
  setImmediate(() => heavyComputation(data));
}


性能

内存

NodeJs的垃圾回收和浏览器端差不多,额外有一些指令:

  • node --expose-gc xxx.js
    允许在代码中手动调用global.gc()触发垃圾回收。
  • node --trace_gc xxx.js
    node --trace_gc_nvp xxx.js
    打印垃圾回收的情况
  • 生成内存快照
    可用于查看内存中的对象数量、大小,并进行定位。
const { writeHeapSnapshot } = require('node:v8');//此处"node:"是NodeJs12+的新增语法,用于防止引用的模块重名
v8.writeHeapSnapshot()
  • 调整新/旧堆空间大小:
node --max-semi-space-size=64 app.js
node --max-old-space-size=4096 app.js
  • 调整垃圾收集频率(ms)
node --gc-interval=100 app.js
  • process.memoryUsage
    查看当前进程内存使用情况
process.memoryUsage()
{
  rss: 35438592,
  heapTotal: 6799360,
  heapUsed: 4892976,
  external: 939130,
  arrayBuffers: 11170
}

CPU
  • 使用V8内置分析器

使用--prof启动

node --prof xxx.js

将得到的log文件转化成txt格式:

node --prof-process isolate-0xnnnnnnnnnnnn-v8.log > processed.txt
  • 使用火焰图

调试

  1. 使用--inspect指令进行调试
    通过node --inspect xxx.js指令启动NodeJs后,进入Chrome浏览器chrome://inspect页面,在Remote Target部分即可使用Chrome DevTools调试NodeJs应用,可以在其中打断点、使用Console、Network、Performance、Memory等。
  2. 在VSCode中进行调试
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容