记录一次 Node.js 应用内存泄露

Posted by csy on 2019-06-27

前言

内存泄漏对于前端的影响通常都是发生在客户端上,而且基本上现代浏览器也会做好保护机制,一般自行刷新之后都会解决。但是,一旦后端内存泄漏造成宕机之后,久而久之整个服务器都会受影响,甚至瘫痪。虽然可以用pm2设置一个内存值上限,超过自动重启,但是知道有问题却一直未解决的感觉太难受了。还是要找到问题的根源,为什么会发生内存泄露?哪个变量发生了内存泄露?

一、什么是内存泄露

1、内存的生命周期
  • 分配我们所需要的内存
  • 用分配到的内存做点什么
  • 不用时进行释放回收

一个内存的生命周期是系统先借给我们一块内存,我们用这块内存搞事情,用过之后再还给系统。而内存泄漏就是这块内存用过之后却不还给系统了。

刚刚提到内存的生命周期,最后一个阶段是不用时进行释放回收,那我们的系统如何判断这块内存不用了?

2、垃圾回收机制

js有个垃圾回收机制,会按照固定的时间间隔,周期性的找出不再继续使用的变量,然后释放其占用的内存。语言引擎有一张”引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,就可以将这块内存释放。

内存的分配以及内存的回收完全实现了自动管理,我们不用操心这种事情。还怎么还会出现内存泄漏的问题呢? 为什么我们不用这块内存了,但是引用次数不是0呢?主要是以下几种情况

  • 闭包
  • 全局变量不会被当成垃圾回收
  • 被遗忘的定时器和回调函数 在定时器完成工作的时候,手动清除定时器。

二、内存泄漏排查以及填坑

1、情境

before Memory Usage 从 232MB 过了 20h 后稳步提升到 1.17G,妥妥的内存泄漏。然而运维的监控是按整个容器颗粒度的,知道了内存泄漏并不能直接知道问题所在,但是缩小了问题域,导致服务器内存泄漏可能有几种原因:

  • 代码编写不当。
  • 使用的外部模块有内存泄漏。
  • 使用的外部模块的C++部分有内存泄漏。这个最难排查。
  • 生产速度大于消费速度,堆积起来,这个一般涉及到IO。比如数据库查询、外部api调用、日志打印写磁盘等。
2、背景

首先排查代码编写不当,项目是 koa + (vue|vue ssr)。看了一些文章,排查 node.js 的内存泄漏,一般是用工具抓内存快照,然后进行分析。有很多工具:devtool、memwatch、memwatch-next、heapdump,试了memwatch是个死项目,很久没维护了无法安装,试了memwatch的下一个版本memwatch-next,也无法安装。试了heapdump,虽然安装的过程比较坎坷,但至少安装成功了!然后就是抓快照,过一段时间后,用快照传到chrome的Memory进行分析snapshot。

另外pm2 monit 可以实时查看容器的内存使用量、CPU等。由于项目 vue ssr 的页面不多,在本地访问其中几个页面时,可以看到 pm2 monit 上的内存使用率瞬间多了十几兆,但关闭页面后,内存并没有回收,然后就code review 对应的页面,查看是否有内存泄露的代码进行修改。

monit

3、vue ssr 可能造成泄漏的位置

1)SSR 的生命周期里只有 beforeCreate 和 created

2)路由守卫处
跟生命周期不同,所有的 route guard 都会在 SSR 运行。分别是

  • beforeEach
  • beforeRouteUpdate
  • beforeEnter
  • beforeRouteEnter
  • beforeResolve
  • afterEach
  • beforeRouteEnter

3) Global Mixin
global mixin 会给每个 Vue 实例一个拷贝,而不是引用

三、总结

修改后的内存监控曲线情况有所好转。 after