请谈一下内存泄漏是什么,以及常见内容泄漏的原因和排查的方法

3 阅读3分钟

内存泄漏

内存泄漏是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。

如果内存泄漏的位置比较关键,那么随着处理的进行可能持有越来越多的无用内存,这些无用内存变多会引起服务器响应速度变慢。

严重的情况下导致内存达到某个极限(可能是进程的上限,如 V8 的上限;也可能是系统可提供的内存上限)会使得应用程序崩溃。

常见内存泄漏的原因以及情况

  • 全局变量 a=10 //未被声明的对象

    global.b = 11;//全局变量的引用,全局变量直接挂在 root 上,不会被清除。

  • 闭包

    function out(){
        const bigData = new BBuffer(100);
        inner = function() {
            // ...
        }
    }
    

    闭包会引用父级函数中的变量,如果闭包未被释放,就会导致内存泄漏,上面的例子 inner 直接挂在 root ,那么每次执行 out 函数所产生的 bigData 都不会释放,从而导致内存泄漏。

    这里的例子只是简单的将引用挂在全局对象上,实际的业务情况可能是挂在某个可以从 root 追溯到的对象上导致的。

  • 事件监听

    NodeJS的事件监听也可能出现内存泄漏。例如对同一个事件重复监听,忘记移除将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现,所以事件重复监听可能收到如下警告:

emitter.setMaxListener() to increase limit

例如,NodeJS 中的 Agent 的 keepAlive 为 true 时, 可能造成内存泄漏。当 Agent keeplive 为 true 时,将会复用之前使用过的 socket,如果在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将会导致事件重复监听从而导致内存泄漏。

原理上与前一个添加事件时忘记清除是一样的。在使用 NodeJS 的 HTTP 模块时,不通过 Keeplive 复用是没问题的,复用了以后就可能会产生内存泄漏。所以,,你需要了解添加事件监听的对象生命周期,并注意手动移除。

排查方法

想要定位内存泄漏,通常有两种情况:

  • 对于只要正常使用就可以重现的内存泄漏,这是很简单的情况。在测试环境模拟就可以排查了。

  • 对于偶然的内存泄漏,一般会与特殊的输入有关系。想稳定重现这种输入是很耗时的过程。如果不能通过代码的日志定位到这个特殊的输入,那推荐去生产环境打印内存快照。

    打印内存快照是很耗 CPU 的操作,可能会对线上业务造成影响。快照工具推荐使用 heapdump 用来保存内存快照,使用 devtool 来查看内存快照。

    使用 deapdump 保存内存快照时,只会有 NodeJS 环境中的对象,不会收到干扰(如果使用 node- inspector 的话,快照中会有前端的变量干扰。)

    PS: 安装 deapdump 在某些 NodeJS 版本上可能出错,建议使用 npm i install heapdump -target = NodeJS 来安装。