在這篇文章中,你能夠學到 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.

原文連接