跟浏览器js不同, 以下代码会找出内存泄露
var theThing = null var replaceThing = function () { var originalThing = theThing var unused = function () { if (originalThing) console.log("hi") } theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log(someMessage) } }; }; setInterval(replaceThing, 1000)
这篇文章中,你可以学到 Node.js 的垃圾回收 (以下简称 GC ) 是怎么工作的,你写下的代码在后台发生了什么,以及内存是如何释放的。
Node.js 应用中的内存管理
每个应用都需要内存才能正常运行。内存管理能动态的分配内存块给需要的程序,在不需要时释放掉,以便能重复使用。
应用级的内存管理可以是手动或自动的。而自动内存管理往往涉及到 GC。
下面的代码片段展示了在 C 中如何使用手动内存管理分配内存:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char name[20];
char *description;
strcpy(name, "RisingStack");
// memory allocation
description = malloc( 30 * sizeof(char) );
if( description == NULL ) {
fprintf(stderr, "Error - unable to allocate required memory\n");
} else {
strcpy( description, "Trace by RisingStack is an APM.");
}
printf("Company name = %s\n", name );
printf("Description: %s\n", description );
// release memory
free(description);
}
|
在手动内存管理中,开发者有责任释放闲置的内存,这种内存管理方式可能会造成下面几个问题:
- 内存泄露,当从不释放使用过的内存时发生
- 野指针,当对象被释放时,而原来的指针仍继续使用。在其他数据覆盖写入或读取敏感信息时会造成严重的安全问题
值得庆幸的是,Node.js 附带了一个垃圾回收器,你不需要去手动管理内存分配
GC 的理念
GC 是一种自动管理应用内存的方法。GC 的工作是回收被未使用的对象所占用的内存。它在 1959 年首次应用于 John McCarthy 创造的 LISP 中。
GC 判断对象不再使用的方式是 没有其他的对象引用它们。 a = o, a = null, o就回收了
GC 前的内存
你的内存看上去如下图所示,如果你有一些互相引用的对象以及一些没有任何引用的对象。
这些没有引用的对象会在 GC 运行 时被回收。
![memory-state-before-node-js-garbage-collection](http://ogovd1xl2.bkt.clouddn.com/memory-state-before-node-js-garbage-collection.png)
GC 后的内存
当 GC 运行起来,无法访问 (没有引用) 的对象会被删除,同时释放掉相应的内存空间。
![memory-state-after-node-js-garbage-collection](http://ogovd1xl2.bkt.clouddn.com/memory-state-after-node-js-garbage-collection.png)
GC 的优点
- 防止了野指针 bug
- 不用担心内存的二次释放
- 避免了一些类型的内存泄露
当然,使用 GC 不能解决你所有的问题,而且它也不是内存管理的银弹。
使用 GC 时需要注意的事项
- 性能影响 - GC 会消耗计算能力去决定什么对象应该释放
- 无法预测的停顿 - 现代 GC 实现尝试去避免 stop-the-world 的回收方式
Node.js GC & 内存管理实践
实践出真知,所以我打算通过几段不同的代码向你展示内存中发生了什么
栈
栈上包含了局部变量和 指向堆上对象 或 指 向应用程序控制流程的指针。
在以下示例中,a和b将会被放置在栈中
function add (a, b) {
return a + b
}
add(4, 5)
|
堆
堆专门用于存储引用类型对象,如字符串?和对象。
在以下示例中,Car 对象将会被放置在栈中
function Car (opts) {
this.name = opts.name
}
const LightningMcQueen = new Car({name: 'Lightning McQueen'})
|
在这之后,内存看起来像这个样子
![node-js-garbage-collection-first-step-object-placed-in-memory-heap](http://ogovd1xl2.bkt.clouddn.com/node-js-garbage-collection-first-step-object-placed-in-memory-heap.png)
让我们添加更多的 Car 对象,看看内存会是什么样子!
function Car (opts) {
this.name = opts.name
}
const LightningMcQueen = new Car({name: 'Lightning McQueen'})
const SallyCarrera = new Car({name: 'Sally Carrera'})
const Mater = new Car({name: 'Mater'})
|
![node-js-garbage-collection-second-step-more-elements-added-to-the-heap](http://ogovd1xl2.bkt.clouddn.com/node-js-garbage-collection-second-step-more-elements-added-to-the-heap.png)
如果GC现在运行,由于根有对每个对象的引用,没有对象会被释放。
让我们添加一些零件到我们的汽车里 (Car 对象) 使它更有趣一点
function Engine (power) {
this.power = power
}
function Car (opts) {
this.name = opts.name
this.engine = new Engine(opts.power)
}
let LightningMcQueen = new Car({name: 'Lightning McQueen', power: 900})
let SallyCarrera = new Car({name: 'Sally Carrera', power: 500})
let Mater = new Car({name: 'Mater', power: 100})
|
![node-js-garbage-collection-assigning-values-to-the-objects-in-heap](http://ogovd1xl2.bkt.clouddn.com/node-js-garbage-collection-assigning-values-to-the-objects-in-heap.png)
如果我们不再使用 Mater,但是重新定义并对它赋值 (如Mater = undefined) 会发生什么?
![node-js-garbage-collection-redefining-values](http://ogovd1xl2.bkt.clouddn.com/node-js-garbage-collection-redefining-values.png)
结果就是,无法从根上访问 Master 对象。所以当下一次 GC 运行时,它将会被释放:
![node-js-garbage-collection-freeing-up-unreachable-object](http://ogovd1xl2.bkt.clouddn.com/node-js-garbage-collection-freeing-up-unreachable-object.png)
现在我们了解了 GC 预期行为的基础,那让我们看看它在 V8 中是如何实现的。
GC 方法
在我们之前的一篇文章中,我们讨论了 Node.js GC 方法是如何工作的,所以我强烈建议去阅读这篇文章。
- 新生区和老生区
- 新生代 (Young Generation)
- Scavenge 和 标记删除
一个真实的例子 — The Meteor Case-Study
在 2013 年,Meteor 的作者宣布了他们碰到的关于内存泄露的发现 ?,问题代码如下所示:
var theThing = null
var replaceThing = function () { var originalThing = theThing
var unused = function () { if (originalThing) console.log("hi") }
theThing = { longStr: new Array(1000000).join('*'), someMethod: function () { console.log(someMessage) } };
};
setInterval(replaceThing, 1000)