认识
一、认识
在 Node.js
中,内存泄漏 指的是程序在执行过程中 未能正确释放不再使用 的内存,从而导致 内存持续增长并最终耗尽系统资源。内存泄漏会影响程序的性能,导致应用变慢,甚至崩溃。
二、场景
-
定时器:
setTimeout
没有被清理,导致引用持续存在,内存无法释放。需要使用clearTimeout
清理定时器;setInterval
没有被清理,导致引用持续存在,内存无法释放。需要使用clearInterval
清理定时器 -
闭包引用: 闭包中对变量的引用被保留,导致内存无法被释放。避免不必要的闭包引用,确保及时释放大对象。
-
数组引用: 数组持续通过
push
、append
等操作新增、插入数据,而由于存在强引用,这些对象无法被垃圾回收器(GC
)清理。所以导致,请求越多,那么leaks
占用的内存越来越大。 -
事件监听器:
EventEmitter
添加事件监听器后,未适时移除,导致事件堆积,无法释放内存。 -
未关闭资源句柄:
fs.open()
返回的文件句柄一定要通过fs.close
关闭;net.createServer()
返回的网络服务器句柄;zlib.createGzip()
返回的压缩句柄;dgram.createSocket()
返回的UDP socket
句柄;crypto.createHash()
返回的哈希句柄;child_process.spawn()
返回的子进程句柄 -
错误处理不完善: 异步回调函数出现错误,但未正确释放资源。添加完善的错误处理,确保资源总能被释放。
-
强引用
Set/Map
-
缓存数据无限存储: 应用程序缓存过多的数据,导致内存不断占用。
三、定位
测试环境: 通过 node --inspect
开启 Node
调试模式, 打开 Chrome
浏览器,在地址栏输入 chrome://inspect
, 打开专用 DevTools
窗口, 根据可疑时间点, 录制内存快照(Heap Snapshot
)。 通过 Summary
或者 Comparison
来定位可疑对象。在 Comparison
视图中选择两个堆快照,并在它们之间进行比较。您可以查看哪些对象在两个堆快照之间新增,哪些对象在两个堆快照之间减少,以及哪些对象的大小发生了变化。Comparison
视图还允许查看对象之间的关系,以及对象的详细信息,如类型、大小和引用计数。通过这些信息,可以了解哪些对象是导致内存泄漏的原因。在 Comparison
视图中查找可疑对象,用于比较当前快照和之前快照的内存变化。主要关注 Comparison.Delta
对象数量的净增量和 Comparison.Size Delta
对象分配的内存变化量。然后通过 Comparison.Retainers
引用链 显示某个对象被哪些对象引用,为什么无法被回收。
生产环境: heapdump
模块可以在运行时生成堆快照。堆快照可导入到 Chrome DevTools
进行分析,找到泄漏原因。监控内存使用情况,自动生成 heap snapshot
,并将快照上传到远程服务器。它还包含了手动生成 heap snapshot
和触发垃圾回收(GC
)的功能,帮助开发者定位和排查内存泄漏问题。
-
应用启动时, 生成启动的
Heapdump
快照, 并开始定时监控内存使用情况 -
针对可疑路由添加
memoryUsageMiddleware
、trackRequestCountMiddleware
中间件。memoryUsageMiddleware
:定期检查内存使用情况并触发生成heap snapshot
, 比如内存每达到100M
生成一次heap snapshot
。trackRequestCountMiddleware
:根据请求计数触发heap snapshot
, 比如每 请求100
次生成一次heap snapshot
。 -
生成的
heap snapshot
会保存在本地目录heapdump
, 并通过scp
上传到指定的远程服务器目录。生成heap snapshot
会主动调用gc
。通过v8 global.gc()
允许你主动触发垃圾回收,让V8
进行内存清理。这对于 诊断内存泄漏 和 生成更准确的堆快照 非常有用。在某些场景下,通过手动触发GC
,可以释放不再使用的内存,确保堆快照中只保留真实的内存占用情况, 如果手动触发GC
后,内存仍然无法释放,说明存在未被回收的对象,即可能存在内存泄漏。 -
提供了
/heap-snapshot
接口,开发者可以手动触发heap snapshot
的生成。提供了/trigger-gc
开发者可以手动触发垃圾回收。
但是在生产环境中, Heapdump
生成快照 会消耗大量的内存和 CPU
,从而会影响服务的正常运行。有时候, Node
服务会中断,根据当时服务器内存大小这个时间会在 2 ~ 30min
左右。确保服务高可用性和生成 Heapdump
快照的核心策略是将快照生成的资源消耗隔离。
-
可以通过扩展
Pod
副本数,通过负载均衡消除单个Pod
性能下降的影响。: 通过Service
将Node.js
服务暴露给外部访问,并结合Deployment ReadinessProbe
确保Service
仅将流量分发给健康的Pod
, 保证服务稳定和服务高可用。配置Deployment replicas
, 配置Node.js Pod
多个副本, 在生成Heapdump
之前,通过Kubernetes
修改Pod
的readinessProbe
,让流量从负载均衡中移除, 将当前Pod
的流量隔离,防止因性能下降影响请求响应。这样即使某个Pod
性能受损,服务流量会自动分配到其他健康的Pod
,保证服务的高可用性。 -
可以使用
child_process
独立进程分离快照生成逻辑,避免主服务受影响。: 将生成Heapdump
的任务交给一个独立的进程,避免主服务受到影响。快照生成由独立的脚本或服务运行。主服务通过异步调用启动快照生成服务。 -
可以使用
Job
或 分离快照生成逻辑,避免主服务受影响。: 通过Kubernetes
的Job
或CronJob
创建临时Pod
专门用于生成Heapdump
,而不是在主服务Pod
中生成。Heapdump
的生成在独立的Pod
中进行,完全不影响主服务。