资讯专栏INFORMATION COLUMN

JavaScript 内存管理及 V8 优化

wenhai.he / 3158人阅读

摘要:内存管理具有垃圾自动回收机制简称。标记清除标记清除是目前大部分引擎使用的判断方式,通过标记变量的状态来确定是否可被回收。被标记,进入环境被标记,进入环境执行完毕之后被标记,离开环境引用计数引擎维护一张引用表,保存内存中所有的资源的引用次数。

JavaScript 内存管理

JavaScript 具有垃圾自动回收机制(Garbage Collection)简称 GC。垃圾回收机制会中断整个代码执行,释放不可能再被使用的变量,释放内存,这个工作机制是周期性的,我们会在下文详细探讨。

可释放对象
function fn1() {
  var obj1 = { name: "xiaomuchen", age: "20" }
}
function fn2() {
  var obj2 = { name: "xiaomuchen", age: "20" }
  return obj2
}
var a = fn1()
var b = fn2()
console.log(a, b) // undefined, {name: "xiaomuchen", age: "20"}

我们对比上面两个函数,fn1 在函数内声明变量 obj1 并且赋值,在函数执行后这个变量便不可再访问了,fn2 在最后把函数内的变量 obj2 返回到全局变量 b,所以 { name: "xiaomuchen", age: "20" } 这个对象(或者说 obj2)依然可被访问

JavaScript 回收机制通过判断变量是否可被访问,来决定回收哪些变量。

标记清除和引用计数

那么 JavaScript 是如何判断变量是否可被访问?这就要提到标记清除和引用计数。

标记清除:标记清除是目前大部分 JavaScript 引擎使用的判断方式,通过标记变量的状态来确定是否可被回收。当变量在环境中被声明时标记进入环境,理论上永远不要释放进入环境的变量,因为它可以在环境中的任何位置、任何时刻被访问。当环境被销毁(如函数执行完),则变量被标记离开环境等待回收。

function fn(){
  var a = { count: 10 } // 被标记,进入环境 
  var b = { count: 20 } // 被标记,进入环境
}
fn(); // 执行完毕之后 b 被标记,离开环境

引用计数:JavaScript 引擎维护一张引用表,保存内存中所有的资源的引用次数。资源被引用一次则引用 +1,资源被去掉引用或者退出变量的函数作用域时,则引用 -1,当资源的引用次数为0时,说明无法访问这个值,则等待回收。
(注:引用计数从 1 到 0 这个过程可能不执行,而是直接标记可被回收,不再进行加减运算节约开销)

function fn(){
  var a = { count: 10 } // 资源 { count: 10 } 被引用次数为 1
  a = { count: 20 } // 资源 { count: 20 } 被引用次数为 1,资源 { count: 10 } 被引用次数为 0,等待回收
  // do someThing
}
fn(); // 资源 { count: 20 } 被释放

但是引用计数存在一种循环引用的情况,如下例子,两个对象之间相互引用,在离开环境后对象不可访问,但由于对象的引用次数为 1,则导致不会被回收。这个例子来自《JavaScript 高级程序设计》,但我思考良久,如果引用计数把 a.param 也作为一个变量来计数,那么就没有这个问题了,引用计数实现的方式不同,产生的结果也不一样。

function fn(){
  var a = { count: 10 }
  var b = { count: 20 }
  a.param = b // b 的引用次数为 2
  b.param = a // a 的引用次数为 2
}
fn(); // a、b 的引用次数为 1
GC 的缺陷、分代回收和增量 GC

和其他语言一样 GC 会中断代码执行,停止其他操作。因为要遍历所有对象,回收所有不可访问对象,这个操作的耗时可能有 100ms 以上。在 V8 引擎新版本中引入了两种优化方法:1. 分代回收(Generation GC),2. 增量 GC(increment GC)

分代回收:目的是通过对象的使用频率、存在时长区分新生代与老生代对象。多回收新生代区(young generation),少回收老生代区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时

增量 GC:把需要长耗时的遍历、回收操作拆分运行,减少中断时间,但是会增大上下文切换开销

Node.js 中的 GC 表现

当我们用 Node.js 搭建一个稳定的服务时,就需要考虑服务器内存的开销,下面一个 Node.js 内存回收执行的例子:

执行代码node --trace_gc --trace_gc_verbose test.js跟踪一个网络服务的 GC。

[41204:0x102001c00] Memory reducer: call rate 0.056, low alloc, foreground
[41204:0x102001c00] Memory reducer: started GC #1
[41204:0x102001c00] Heap growing factor 1.1 based on mu=0.970, speed_ratio=42956 (gc=675253, mutator=16)
[41204:0x102001c00] Grow: old size: 21382 KB, new limit: 33604 KB (1.1)
[41204:0x102001c00] Memory reducer: finished GC #1 (will do more)
[41204:0x102001c00]   156410 ms: Mark-sweep 27.7 (50.0) -> 21.0 (30.0) MB, 12.4 / 0.0 ms (+ 20.4 ms in 7 steps since start of marking, biggest step 4.8 ms) [Incremental marking task: finalize incremental marking] [GC in old space requested].
[41204:0x102001c00] Memory allocator,   used:  30756 KB, available: 1435612 KB
[41204:0x102001c00] New space,          used:    169 KB, available:    838 KB, committed:   1024 KB
[41204:0x102001c00] Old space,          used:  16662 KB, available:   2417 KB, committed:  19412 KB
[41204:0x102001c00] Code space,         used:   4078 KB, available:    178 KB, committed:   5120 KB
[41204:0x102001c00] Map space,          used:    642 KB, available:      0 KB, committed:   2128 KB
[41204:0x102001c00] Large object space, used:      0 KB, available: 1434571 KB, committed:      0 KB
[41204:0x102001c00] All spaces,         used:  21552 KB, available: 1438005 KB, committed:  27684 KB
[41204:0x102001c00] External memory reported:   1026 KB
[41204:0x102001c00] Total time spent in GC  : 158.6 ms
[41204:0x102001c00] Memory reducer: call rate 0.003, low alloc, foreground

首先我们可以看到 Node.js 区分 New spaceOld space 等来划分检索空间。而提示(+ 20.4 ms in 7 steps since start of marking, biggest step 4.8 ms) 告诉我们这个标记的步骤分 7 步进行,耗时最长的一次时 4.8ms。这使 JavaScript 可以很好的支持开发高实时应用,原文。

总结

因为篇幅有限,留下一些小问题供大家思考:

闭包一定会导致内存不可被回收?

如何监控一个 Node.js 服务的内存开销,如何处理不可预知的内存泄漏?

作者:肖沐宸,github。

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

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

相关文章

  • JavaScript、Node.js与V8的关系

    对于了解Node的开发人员,我们都知道Node是基于Chrome V8引擎开发的能使JavaScript在服务器端运行的运行时环境(runtime environment)。一方面,它提供了多种可调用的API,如读写文件、网络请求、系统信息等。另一方面,因为CPU执行的是机器码,它还负责将JavaScript代码解释成机器指令序列执行,这部分工作是由V8引擎完成。 Motivation JavaS...

    Cciradih 评论0 收藏0
  • JavaScript 工作原理之六-WebAssembly 对比 JavaScript 其使用场景

    摘要:现在,我们将会剖析的工作原理,而最重要的是它和在性能方面的比对加载时间,执行速度,垃圾回收,内存使用,平台访问,调试,多线程以及可移植性。目前,是专门围绕和的使用场景设计的。目前不支持多线程。 原文请查阅这里,略有改动,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland。 本系列持续更新中,Github 地址请查阅这里。 这是 JavaScript 工作原理的第六章...

    jay_tian 评论0 收藏0
  • javascript典型内存泄漏chrome的排查方法

    摘要:的内存泄漏对于这门语言的使用者来说,大多数的使用者的内存管理意识都不强。内存泄漏的定义指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。 javascript的内存泄漏 对于JavaScript这门语言的使用者来说,大多数的使用者的内存管理意识都不强。因为JavaScript一直以来都只作为在网页上使用的脚本语言,而网页往往都不会长时间的运行,所以使用者对JavaScript的...

    HackerShell 评论0 收藏0
  • JavaScript 开发者所需要知道的 V8(一):V8 In NodeJS

    摘要:欢迎来我的博客阅读开发者所需要知道的一是一款拥有自动垃圾回收功能的编程语言。它随着的第一版发布而发布以及开源。年月,基金宣布和合并,合并版本在未来发布。年月日,官方公布又一个新的名为的优化编译器,主要提供的新语法,以及提高性能。 欢迎来我的博客阅读:「JavaScript 开发者所需要知道的 V8(一):V8 In NodeJS」 Motivation JavaScript 是一款拥有...

    Lemon_95 评论0 收藏0

发表评论

0条评论

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