资讯专栏INFORMATION COLUMN

【译文】Node.js垃圾回收机制-基础

haobowd / 2556人阅读

摘要:正好最近在学习的各种实现原理,在这里斗胆翻译一篇垃圾回收机制原文链接。自动管理的机制中,通常都会包含垃圾回收机制。二垃圾回收机制的概念垃圾回收,是一种自动管理应用程序所占内存的机制,简称方便起见,本文均采用此简写。

最近关注了一个国外技术博客RisingStack里面有很多高质量,且对新手也很friendly的文章。正好最近在学习Node.js的各种实现原理,在这里斗胆翻译一篇Node.js垃圾回收机制(原文链接)。

正文

在这篇文章中,你将会学习Node.js的垃圾回收(garbege collection)机制是如何工作的;即在你敲代码的时候,后台是怎么帮你清空内存里的垃圾的。

一、Node.js应用的内存管理

内存的适当分配,对于所有应用都至关重要。内存管理的任务,就是在程序请求内存的时候动态地为它们分配内存块;并在程序不再需要内存的时候释放掉。

应用级别的内存管理,有手动管理自动管理两种模式。自动管理的机制中,通常都会包含垃圾回收机制

The following code snippet shows how memory can be allocated in C, using manual memory management:

下面的代码片段展示了C语言中内存是如何分配的,这属于手动管理:

#include 
#include 
#include 

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
");
   } else {
      strcpy( description, "Trace by RisingStack is an APM.");
   }

   printf("Company name = %s
", name );
   printf("Description: %s
", description );

   // release memory
   free(description);
}

在手动内存管理机制中,释放无用内存的任务落在了程序猿身上。这样可能会给应用带来严重的问题:

内存泄露:可能某些占用的内存一直没有被释放。

当一个对象被删除(过早释放)的时候,可能会有指针不指向任何有效的对象,但仍然指向原来的内存。这种指针被称为“悬挂指针”。这个时候如果再去使用这段内存,就会产生严重的安全问题。

但幸运的是,Node.js是自带垃圾回收机制的,所以你不需要手动管理内存。

二、垃圾回收机制的概念

垃圾回收,是一种自动管理应用程序所占内存的机制,简称“GC”(方便起见,本文均采用此简写)。它的任务,就是回收无用对象(即垃圾)所占用的内存。它第一次出现,是在1959年的LISP语言中,由John McCarthy发明。

GC判断一个对象为垃圾的标准是:是否还有其他对象引用它。

The way how the GC knows that objects are no longer in use is that no
other object has references to them.

如果没有GC

下图展示了没有垃圾管理机制的时候,内存的情况。可以看到有的对象与其余的对象之间,没有任何引用关系,但他们的内存也不会被回收。

有了GC之后

有了GC之后,没有引用关系的对象占用的内存,都会被GC悄然回收。

使用GC的优势

it prevents wild/dangling pointers bugs,
it won"t try to free up space that was already freed up,
it will protect you from some types of memory leaks.
Of course, using a garbage collector doesn"t solve all of your problems, and it’s not a silver bullet for memory management. Let"s take a look at things that you should keep in mind!

避免了悬挂指针的出现。

它不会尝试去重复释放并没有被占用的内存。

它会防止某些类型的内存泄露。

当然了,GC并不能解决所有内存相关的问题,它不是解决内存管理问题的万金油。有些使用GC的注意事项还是需要开发者牢记:

performance impact - in order to decide what can be freed up, the GC consumes computing power
unpredictable stalls - modern GC implementations try to avoid "stop-the-world" collections

对性能的影响:在判断哪些内存要释放的时候,GC会占用CPU资源。

不可预测的中断:尽管现在的GC都会避免“停止一切”的情况发生,但是还是不可避免的会出现。

译注:“停止一切”(stop-the-world)是指当垃圾收集没有结束前,内存对于外部的请求是不会进行响应的,直到收集完毕应用才会继续响应请求。

三、Node.js垃圾回收&内存管理实践

学代码就是要写代码,下面就用几段代码展示本节的主题。首先介绍几个基本概念:

栈(Stack)

中存储着本地变量、指向堆中对象的指针、定义应用程序控制流的指针。

在下面的例子中,变量a、b都会存储在栈中。

function add (a, b) {  
  return a + b
}

add(4, 5)  
堆(Heap)

专门用于存储“引用类型”的对象,例如字符串或对象。

下例中的Car对象就是保存在堆中的。

function Car (opts) {  
  this.name = opts.name
}

const LightningMcQueen = new Car({name: "Lightning McQueen"})  

执行之后,内存看上去会是这个样子:

新建更多的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"})  

如果这个时候执行垃圾回收,那么什么都不会发生,因为根对象(root)对每个对象都有引用。

那现在把上述例子再复杂化一点,给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})  

What would happen, if we no longer use Mater, but redefine it and assign some other value, like Mater = undefined?

现在,如果我们不想再使用Mater这个实例,把他赋一个别的值,比如Mater = undefined。这时会发生什么?

可以看到Mater失去了root对他的引用。那么,在下次垃圾回收执行的时候,它的内存就会被释放。

好了,现在我们都理解了GC的基本原理和执行方式,来看看V8引擎中的GC是如何实现的吧!

垃圾回收方法

在我们之前的一篇文章中,我们介绍过Node.js的垃圾回收方法是如何工作的,我强烈建议阅读此文章。

这篇文章的要点如下:

1. 新生代空间 & 老生代空间

堆中存在两个“段”(segment),新生代空间(New Space)和老生代空间(Old Space)。新的内存分配都发生在新建空间中,它只有1-8MBs左右大,但垃圾回收却很迅速和频繁。这里存储的对象称为“新生代”(Young Generation)。

老生代空间中,存储着那些新生代空间中未被回收,晋升至此的对象。它们被称为“老生代”(Old Generation)。这里内存分配非常频繁,但垃圾回收的成本却很高,因此执行地不那么频繁。

2. 新生代

通常只有20%左右的新生代会晋升为老生代。老生代空间只有在快被耗尽的时候,才会执行垃圾回收。V8引擎采用了两种回收算法来实现:Scavenge 和 Mark-Sweep 。

Scavenge回收算法运算速度很快,用于新生代;慢一些的Mark-Sweep算法用于老生代。

四、现实案例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.

通常来说,实现闭包的方式为:每个函数对象都链接到一个字典式的对象,此对象表现其词法作用域。如果replaceThing中两个函数都使用了变量originalThing,那么即便originalThing被多次赋值,也必须保证这两个函数得到的永远是同一个对象,才能保证两个函数共享一个词法作用域。那么问题来了,Chrome的V8
JavaScript引擎只有在一个变量没有被用在任何闭包中的时候,才会将其隔离在词法环境之外。 - Meteor blog.

更多相关阅读

Finding a memory leak in Node.js
JavaScript Garbage Collection Improvements - Orinoco
memorymanagement.org

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91262.html

相关文章

  • 译文-java垃圾回收机制

    摘要:原文出处垃圾回收机制标记清除算法介绍最主要的理论算法之一,在实践过程中,为了真实情景需要,需要许多调整。因此不会仅仅标记清除,垃圾回收期间,内存整理进程同时在工作。不同内存区域的垃圾收集机制不辣么容易理解。 原文出处:java垃圾回收机制 标记清除算法介绍最主要的理论算法之一,在实践过程中,为了真实情景需要,需要许多调整。举一个简单例子,我们检查JVM需要做的各种事情,以便我们安全地去...

    Warren 评论0 收藏0
  • 译文-垃圾回收器是什么

    摘要:垃圾回收器追踪所有正在使用的对象,将无用对象标记为垃圾。自动化指针内存回收自动化的最好方式之一是使用钩子函数。它们可能因为多种原因发生,但是这种垃圾回收器是最主流的一种。 原文出处:What Is Garbage Collection? 一眼就应该从名称看出垃圾回收机制的含义-查找垃圾,然后丢弃。事实正好相反。垃圾回收器追踪所有正在使用的对象,将无用对象标记为垃圾。请留意,我们开始研究...

    alanoddsoff 评论0 收藏0
  • 简述JavaScript的垃圾回收机制

    摘要:关键是释放内存这一步,各种语言都有自己的垃圾回收简称机制。用的是这种,在字末位进行标识,为指针。对于而言,最初的垃圾回收机制,是基于引用计次来做的。老生代的垃圾回收,分两个阶段标记清理有和这两种方式。 不管是高级语言,还是低级语言。内存的管理都是: 分配内存 使用内存(读或写) 释放内存 前两步,大家都没有太大异议。关键是释放内存这一步,各种语言都有自己的垃圾回收(garbage ...

    wenshi11019 评论0 收藏0
  • javascript 垃圾回收算法

    摘要:它将堆内存一分为二每一部分空间称为。以的垃圾回收堆内存为例做一次小的垃圾回收需要毫秒以上做一次非增量式的垃圾回收甚至要秒以上。这是垃圾回收中引起线程暂停执行的时间在这样的时间花销下应用的性能和响应能力都会直线下降。 我们通常理解的 javascript 垃圾回收机制都停留在表面,会释放不被引用变量内存,最近在读《深入浅出node.js》的书,详细了解了下 v8 垃圾回收的算法,记录了一...

    simon_chen 评论0 收藏0
  • Node - 内存管理和垃圾回收

    摘要:的内存限制和垃圾回收机制内存限制内存限制一般的后端语言开发中,在基本的内存使用是没有限制的。的内存分代目前没有一种垃圾自动回收算法适用于所有场景,所以的内部采用的其实是两种垃圾回收算法。 前言 从前端思维转变到后端, 有一个很重要的点就是内存管理。以前写前端因为只是在浏览器上运行, 所以对于内存管理一般不怎么需要上心, 但是在服务器端, 则需要斤斤计较内存。 V8的内存限制和垃圾回收机...

    joyqi 评论0 收藏0

发表评论

0条评论

haobowd

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<