在这篇文章中,你能够学到 Node.js 的垃圾回收 (如下简称 GC ) 是怎么工做的,你写下的代码在后台发生了什么,以及内存是如何释放的。java


ancient-garbage-collector-in-action ancient-garbage-collector-in-action

Node.js 应用中的内存管理

每一个应用都须要内存才能正常运行。内存管理能动态的分配内存块给须要的程序,在不须要时释放掉,以便能重复使用。node

应用级的内存管理能够是手动或自动的。而自动内存管理每每涉及到 GC。安全

下面的代码片断展现了在 C 中如何使用手动内存管理分配内存:app

#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);
}

手动内存管理中,开发者有责任释放闲置的内存,这种内存管理方式可能会形成下面几个问题:ide

  • 内存泄露,当从不释放使用过的内存时发生
  • 野指针,当对象被释放时,而原来的指针仍继续使用。在其余数据覆盖写入或读取敏感信息时会形成严重的安全问题

值得庆幸的是,Node.js 附带了一个垃圾回收器,你不须要去手动管理内存分配post

GC 的理念

GC 是一种自动管理应用内存的方法。GC 的工做是回收被未使用的对象所占用的内存。它在 1959 年首次应用于 John McCarthy 创造的 LISP 中。性能

GC 判断对象再也不使用的方式是没有其余的对象引用它们。ui

GC 前的内存

你的内存看上去以下图所示,若是你有一些互相引用的对象以及一些没有任何引用的对象。这些没有引用的对象会在 GC 运行 时被回收。this


memory-state-before-node-js-garbage-collection memory-state-before-node-js-garbage-collection
GC 后的内存

当 GC 运行起来,没法访问 (没有引用) 的对象会被删除,同时释放掉相应的内存空间。


memory-state-after-node-js-garbage-collection memory-state-after-node-js-garbage-collection

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 node-js-garbage-collection-first-step-object-placed-in-memory-heap

让咱们添加更多的 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 node-js-garbage-collection-second-step-more-elements-added-to-the-heap

若是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 node-js-garbage-collection-assigning-values-to-the-objects-in-heap

若是咱们再也不使用 Mater,可是从新定义并对它赋值 (如Mater = undefined) 会发生什么?


node-js-garbage-collection-redefining-values node-js-garbage-collection-redefining-values

结果就是,没法从根上访问 Master 对象。因此当下一次 GC 运行时,它将会被释放:


node-js-garbage-collection-freeing-up-unreachable-object node-js-garbage-collection-freeing-up-unreachable-object

如今咱们了解了 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)

Well, the typical way that closures are implemented is that every function object has a link to a dictionary-style object representing its lexical scope. If both functions defined inside replaceThing actually used originalThing, it would be important that they both get the same object, even if originalThing gets assigned to over and over, so both functions share the same lexical environment. Now, Chrome’s V8 JavaScript engine is apparently smart enough to keep variables out of the lexical environment if they aren’t used by any closures - from the Meteor blog.

原文连接