共计 4768 个字符,预计需要花费 12 分钟才能阅读完成。
这篇文章是由 Mozilla 的 Identity 团队带来的 A Node.JS Holiday Season 系列文章的首篇,该团队上个月发布了 Persona 的第一个测试版本。在开发 Persona 时我们构建了一系列的工具,包括了从调试,到本地化,到依赖管理以及更多的方面。在这一系列的文章中我们将与社区分享我们的经验和这些工具,这对任何想用 node.js 建立一个高可用性服务的人都很有用。我们希望您能喜欢这些文章,并期待看到您的想法和贡献。
我们将从一篇关于 Node.js 的实质性问题:内存泄漏的主题文章开始。我们会介绍 node-memwatch — 一个帮助发现并隔离 Node 中的内存泄漏问题的函数库。
为什么自寻烦恼?
关于追踪内存泄漏问得最多的问题就是,“为什么要自寻烦恼?”。难道没有更紧迫的问题需要先 解决吗?为什么不选择不时地重启服务,或为之分配更多的RAM?为了回答这些问题,我们提出了以下三点建议:
1.也许你不在乎不断增长的内存占用,但V8在乎(V8是Node运行时的引擎)。随着内存泄漏的增长,V8对垃圾收集器越来越具有攻击性,这会使你的应用运行速度变慢。所以,在Node上,内存泄漏会损害程序性能。
2.内存泄漏可能触发其他类型的失败。内存泄漏的代码可能会持续的引用有限的资源。你可能会耗尽文件描述符;你还可能会突然不能建立新的数据库连接。这类问题可能在你的应用耗尽内存前很早就会暴露出来,但它仍然会是你陷入困境。
3.最后,你的应用迟早会崩溃,并且在你的应用受到欢迎时肯定会发生。所有人都会在Hacker News上嘲笑你,讽刺你,这样你就悲剧了。
溃千里之堤的蚁穴在哪里?
在构建复杂应用的时候,很多地方都可能发生内存泄露。闭包可能是最广为人知也是最声名狼藉的。因为闭包保留了对其作用域内的东西的引用,而这正是通常的内存泄露之源。
闭包泄露往往只有在有人去寻找它们的时候才能发现。但是在 Node 的异步世界里,我们随时随地的通过回调函数不停的生成闭包。如果这些回调函数没有在创建后立刻使用,分配的内存就会持续增长,那些看起来没有内存泄露问题的代码也会产生泄露。而这种问题更难发现。
你的应用也可能由于上游代码的问题导致内存泄露。也许你能定位到出现内存泄露的代码,但是你可能只能眼巴巴地盯着你那完美无缺的代码然后困惑于这到底是怎么泄露的!
正是这些难以定位的内存泄露促使我们想要一个 node-memwatch 这样的工具。传说几个月以前,我们的 Lloyd Hilaiel 把他自己锁在一个小房间里两天,试着追踪一个在压力测试下变得非常明显的内存泄露问题。(顺便说下,尽请期待 Lloyd 即将到来的关于负荷测试的文章)
经过两天的努力,他终于发现了 Node 内核中的元凶:http.ClientRequest 中的事件监听器没有被释放。(最终修复这个问题的补丁只有两个但却至关重要的字母)。正是这次痛苦的经历促使 Lloyd 想要写一个能够帮助查找内存泄露的工具。
内存泄露定位工具
现在已经有许多好用且不断增强的工具用于定位 Node.js 应用的内存泄露。下面是其中的一些:
- Jimb Esser 的 node-mtrace,它使用了 GCC 的 mtrace 工具来分析堆的使用。
- Dave Pacheco 的 node-heap-dump 对 V8 的堆抓取了一张快照并把所有的东西序列化进一个巨大的 JSON 文件。它还包含了一些分析研究快照结果的 JavaScript 工具。
- Danny Coates 的 v8-profiler 和 node-inspector 提供了绑定在 Node 中的 V8 分析器和一个基于 WebKit Web Inspector 的 debug 界面。
- Felix Gnass 的未禁用保持器图表分支。
- Felix Geisendörfer 的 Node 内存泄露指导(Node Memory Leak Tutorial)是一个又短又酷的 v8-profiler 和 node-debugger 使用教程。同时也是目前最先进的 Node.js 内存泄露调试技术指南。
- Joyent 的 SmartOS 平台,它提供了大量用于调试 Node.js 内存泄露的工具。
上面的这些工具我们都很喜欢,但是没有一个适用于我们的场景。Web Inspector 对于开发中的应用非常棒,但是很难用于热部署的场景,尤其是在多服务器和涉及子进程的时候。同样的,在长时间高负载运行中出现的内存泄露也很难复现。像 dtrace 和 libumem 这样的工具虽然让人印象深刻,但是不是所有的操作系统都能用。
Enternode-memwatch
我们需要一个跨平台的调试库,当我们的程序可能存在内存泄漏时,它不需要设备告诉我们,并且会帮我们找到哪里存在泄漏。所以我们实现了 node-memwatch。
它给我们提供三件东西:
- 一个‘泄漏’事件发射器
memwatch.on(‘leak’, function(info) {
// look at info to find out about what might be leaking
});
• 一个‘状态事件发射器
var memwatch = require(‘memwatch’);
memwatch.on(‘stats’, function(stats) {
// do something with post-gc memory usage stats
});
• 一个堆内存区分类
var hd = new memwatch.HeapDiff();
// your code here …
var diff = hd.end();
• 并且还有一个在测试时很有用处的,可以触发垃圾收集器的功能。好吧,一共四点。
var stats = memwatch.gc();
memwatch.on(‘stats’, …): Post-GC 堆统计
node-memwatch 能够在任何一个 JS 对象分配之前,紧随着一次完整的垃圾回收和内存压缩发出一个内存使用样本。(它使用了 V8 的 post-gc 钩子,V8::AddGCEpilogueCallback,来在每次垃圾回收触发时收集堆使用信息)
统计数据包括:
- usage_trend(使用趋势)
- current_base(当前基数)
- estimated_base(预期基数)
- num_full_gc(完整的垃圾回收次数)
- num_inc_gc(增长的垃圾回收次数)
- heap_compactions(内存压缩次数)
- min(最小)
- max(最大)
这里有一个展示存在内存泄露的应用的数据看起来是什么样的例子。下面的图表随着时间追踪内存的使用。疯狂的绿线展示了 process.memoryUsage()报告的内容。红线展示了 node_memwatch 报告的 current_base。左下侧的盒子展示了附加信息。
注意 Incr GCs 非常高。那说明 V8 在拼命的尝试清理内存。
memwatch.on(‘leak’, …): 堆分配趋势
我们定义了一个简单的侦测算法来提醒你应用程序可能存在内存泄漏。即如果经过连续五次 GC,内存仍被持续分配而没有得到释放,node-memwatch 就会发出一个 leak 事件。事件的具体信息格式是明了易读的,就像这样:
{start: Fri, 29 Jun 2012 14:12:13 GMT,
end: Fri, 29 Jun 2012 14:12:33 GMT,
growth: 67984,
reason: ‘heap growth over 5 consecutive GCs (20s) – 11.67 mb/hr’ }
memwatch.HeapDiff(): 查找泄漏元凶
最后,node-memwatch 能比较堆上对象的名称和分配数量的快照,其对比前后的差异可以帮助找出导致内存泄漏的元凶。
var hd = new memwatch.HeapDiff();
// Your code here …
var diff = hd.end();
对比产生的内容就像这样:
{
“before”: {
“nodes”: 11625,
“size_bytes”: 1869904,
“size”: “1.78 mb”
},
“after”: {
“nodes”: 21435,
“size_bytes”: 2119136,
“size”: “2.02 mb”
},
“change”: {
“size_bytes”: 249232,
“size”: “243.39 kb”,
“freed_nodes”: 197,
“allocated_nodes”: 10007,
“details”: [
{
“what”: “Array”,
“size_bytes”: 66688,
“size”: “65.13 kb”,
“+”: 4,
“-“: 78
},
{
“what”: “Code”,
“size_bytes”: -55296,
“size”: “-54 kb”,
“+”: 1,
“-“: 57
},
{
“what”: “LeakingClass”,
“size_bytes”: 239952,
“size”: “234.33 kb”,
“+”: 9998,
“-“: 0
},
{
“what”: “String”,
“size_bytes”: -2120,
“size”: “-2.07 kb”,
“+”: 3,
“-“: 62
}
]
}
}
HeapDiff 方法在进行数据采样前会先进行一次完整的垃圾回收,以使得到的数据不会充满太多无用的信息。memwatch 的事件处理会忽略掉由 HeapDiff 触发的垃圾回收事件,所以在 stats 事件的监听回调函数中你可以安全地调用 HeapDiff 方法。
在下图中,我们加上了堆内存对象分配数量排行:
下一步怎么做
node-memwatch 提供了:
- 准确的内存使用情况跟踪
- 疑似内存泄漏通知
- 堆差异比较
- 这是跨平台的
- 并且不要求任何额外的设备
我们想要它的功能更多。特别是,我们希望 node-memwatch 能够提供一些导致内存泄漏的对象的使用案例(例如,变量名称,数组下标或闭包代码)。
我们希望您能在调试 Node 应用程序泄漏问题时发现 memwatch 很好用,也希望您能复制一份代码并帮助我们做得更好。
下面的内容你可能也喜欢:
如何在 CentOS 7 安装 Node.js http://www.linuxidc.com/Linux/2015-02/113554.htm
Ubuntu 14.04 下搭建 Node.js 开发环境 http://www.linuxidc.com/Linux/2014-12/110983.htm
Ubunru 12.04 下 Node.js 开发环境的安装配置 http://www.linuxidc.com/Linux/2014-05/101418.htm
Node.Js 入门[PDF+ 相关代码] http://www.linuxidc.com/Linux/2013-06/85462.htm
Node.js 开发指南 高清 PDF 中文版 + 源码 http://www.linuxidc.com/Linux/2014-09/106494.htm
Node.js 入门开发指南中文版 http://www.linuxidc.com/Linux/2012-11/73363.htm
Ubuntu 编译安装 Node.js http://www.linuxidc.com/Linux/2013-10/91321.htm
Node.js 的详细介绍:请点这里
Node.js 的下载地址:请点这里
英文原文:Tracking Down Memory Leaks in Node.js
本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-08/121555.htm