JavaScript 内存管理

更新日期: 2021-06-13阅读: 1.5k标签: 内存

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 space、Old space 等来划分检索空间。而提示(+ 20.4 ms in 7 steps since start of marking, biggest step 4.8 ms) 告诉我们这个标记的步骤分 7 步进行,耗时最长的一次时 4.8ms。这使 JavaScript 可以很好的支持开发高实时应用。


总结

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

  1. 闭包一定会导致内存不可被回收?
  2. 如何监控一个 Node.js 服务的内存开销,如何处理不可预知的内存泄漏?

作者:肖沐宸,github


链接: https://fly63.com/article/detial/10424

JavaScript 内存管理和垃圾回收

JavaScript 的内存管理和垃圾回收,是个略生僻的话题,因为在JavaScript 中不显式执行内存操作,不过最好了解它如何工作。

js常见的内存泄漏及解决方法总汇

js具有自动回收垃圾的机制,即执行环境会负责管理程序执行中使用的内存。在C和C++等其他语言中,开发者的需要手动跟踪管理内存的使用情况。在编写js代码时候,开发人员不用再关心内存使用的问题,所需内存的分配 以及无用的回收完全实现了自动管理。

浅谈javaScript内存机制

javaScript内存空间并不是一个经常被提及的概念,想要对JS的理解更加深刻,就必须对内存空间有一个清晰的认:栈与堆、复杂数据类型与基本数据类型、引用数据类型与堆内存

js 把一个对象赋值给另一个对象会指向同一个内存地址

实际上并不是新建一个和原对象(数组也是对象)完全一样的对象,而是把原对象的内存地址直接复制给了另一个对象,也就是说两个对象都是指向同一个内存地址,所以实际上它们就是同一个对象。

php底层原理之垃圾回收机制

php垃圾回收机制,对于PHPer来说是一个不陌生但是又不是很熟悉的内容。那么php是怎么实现对不需要的内存进行回收的呢?首先还是需要了解下基础知识,便于垃圾回收原理内容的理解。

js变量、作用域和内存问题

JavaScript变量可以用来保存两种类型的值:基本类性值和引用类性值。所有变量(包括基本类型和引用类型)都存在于一个执行环境(也称为作用域)当中,具有自动垃圾收集机制的编程语言,开发人员不必关心内存分配和回收问题

php中的内存管理

计算机的内存由操作系统进行管理,所以普通应用程序是无法直接对内存进行访问的。应用程序只能向操作系统申请内存,通常的应用也是这么做的,在需要的时候通过类似malloc之类的库函数 向操作系统申请内存。

原生JS与Jquery删除iframe并释放内存-IE

当项目以tab页签方式打开多个iframe窗口时,关闭tab页签同时也需要关闭iframe并释放内存资源,主要是针对IE浏览器。

闭包真的会导致内存泄漏?

今天遇到一个很有争议的问题,在这里分享一下,我相信对于即将面试前端的小伙伴会有帮助的。主要内容是围绕下边的问题展开的,文章涉及到的其他方面的知识点不展开叙述。

Web 应用的内存优化

随着 Web 应用复杂程度越来越高,以及 NodeJS 大规模投入生产环境,许多 Web 应用都会长时间运行, JavaScript 的内存管理显得更为重要。JavaScript 具备自动回收垃圾的机制

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!