前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 垃圾回收

JavaScript 垃圾回收

作者头像
李振
发布2021-11-26 11:45:11
5620
发布2021-11-26 11:45:11
举报
文章被收录于专栏:乱码李

垃圾回收

JavaScript 具有自动垃圾回收机制,这种垃圾回收机制原理其实很简单:找出那些不再继续使用的变量,然后释放其所占用的内存,垃圾回收器会按照固定的时间间隔周期性地执行这一操作。局部变量只有在函数执行的过程中存在,在这个过程中,会为局部变量在栈(或者堆)内存上分配空间,然后在函数中使用这些变量,直至函数执行结束。垃圾回收器必须追踪哪个变量有用哪个没用,对于不再有用的变量打上标记,以备将来回收其占用的内存,用于标识无用变量的策略主要有标记清除法和引用计数法。

JavaScript 内存分配

JavaScript 在定义变量时就完成了内存分配,还可以通过函数调用分配内存:

代码语言:javascript
复制
/**
 * 值的初始化
 */
var s = "azerty" // 给字符串分配内存

var o = {
  a: 1,
  b: null
} // 给对象及其包含的值分配内存

// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"]

function f(a){
  return a + 2
} // 给函数(可调用的对象)分配内存

// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue'
}, false)

/**
 * 函数调用分配内存
 */
var d = new Date() // 分配一个 Date 对象
var e = document.createElement('div') // 分配一个 DOM 元素

使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。

mark and sweep

JavaScript 中最常用的垃圾回收方式就是标记清除(mark-and-sweep),当变量进入环境时,就将这个变量标记“进入环境”,当变量离开环境时,就将其标记为“离开环境”。至于怎么标记有很多种方式,比如特殊位的反转、维护一个列表等。

gc_mark_sweep
gc_mark_sweep

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,然后它会去掉环境中的变量已经被环境中变量被标记为引用的变量,在此之后再被标记的变量将被视为准备删除的变量。最后垃圾回收器清除标记的变量,回收它们所占用的内存空间。

目前主流浏览器都是使用标记清除式的垃圾回收策略,只不过收集的间隔有所不同。

引用计数(refefence counting)

引用计数跟踪几个每个值被引用的次数,当声明一个引用类型值赋给该变量时,则这个值的引用次数就是 1,如果同一个值被赋给另外一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,就可以将其内存空间回收。当垃圾回收器再次运行时,它就会释放哪些引用次数为 0 的值所占用的内存。

Netscape Navigator 3.0 是最早使用引用计数策略的浏览器,它很快就遇到一个严重的问题:循环引用。

代码语言:javascript
复制
function problem () {
  var obj1 = new Object()
  var obj2 = new Object()

  obj1.someOtherObj = obj2
  obj2.anotherObj = obj1    
}

在这个例子中,obj1 和 obj2 通过各自的属性相互引用,也就是说,这两个对象的引用次数都是 2。在采用标记清除策略的现实中,由于函数执行后,两个对象都离开了作用域,因此相互引用不存在问题。

但是在引用计数策略中,当函数执行完毕后,obj1 和 obj2 还得继续存在,因为它们的引用次数永远不会是 0,导致内存无法回收。

Netscape Navigator 4.0 中放弃了引用计数,转而使用标记清除来实现垃圾回收。

IE 存在的问题:

在 IE9 之前,IE 中有一部分对象并不是原生 JavaScript 对象。例如,BOM 和 DOM 中的对象就是 C++ 实现的 COM 对象,而 COM 对象的垃圾收集机制采用的是引用计数策略。因此,即使 IE 中的 JavaScript 引擎使用标记清除策略实现,但是 JS 访问的 COM 对象依然是基于引用计数策略的。可以在 IE 中涉及到 COM 对象,就会存在循环引用的问题。

代码语言:javascript
复制
var ele = document.getElementById('some_element')
var obj = new Object()
obj.ele = ele
ele.someObj = obj

在这个例子中一个 DOM 元素与一个原生 JS 对象之间创建了循环引用,由于 COM 的引用计数的垃圾回收策略,导致例子中的 DOM 从页面删除,也不会被垃圾回收。

解决办法:

代码语言:javascript
复制
obj.ele = null
ele.someObj = null

将变量设置为 null 意味着切断变量和它此前引用值之间的连接。当垃圾回收器下次运行时,就能删除这些值并回收它们占用的内存。

IE9 之后,DOM 和 BOM 对象都被转换成立真正的 JS 对象,这样就避免了两种垃圾回收算法并存导致的问题。

性能问题

垃圾收集器是周期性运行,因此其运行时间间隔是一个非常重要的问题。IE7 之前的垃圾收集器是根据内存分配量运行的,达到某一个临界值(256 个变量,4096 个对象、或者 64 KB 字符串)就是启动垃圾回收器,这导致了一个问题:如果该脚本在其生命周期需要一直保持这么多变量,垃圾回收器就不得不频繁运行。

事实上,浏览器中一般可以主动触发垃圾收集过程。在 IE 中,调用 window.CollectGarbage() 方法会立即执行垃圾收集,在 Opera7 之后的版本中,调用 window.opera.collect() 也会启动垃圾收集。

优化内存

比较好的办法就是执行代码中只保留必要的数据,一旦数据不再有用,通过设置为 null 来释放其引用(dereferencing),适用于大多数全局变量和全局对象的属性。

V8 内存机制

V8 引擎会限制 JavaScript 所能使用的内存大小,64 位系统是 1.4GB,32 位系统是 0.7GB。在 Node 环境中使用下面两个参数可以调整启动时内存限制的大小:

代码语言:javascript
复制
node --max-nex-space-size=1024 app.js // 单位为KB
node --max-old-space-size=2000 app.js // 单位为MB

这两条命令分别对应 Node 内存堆中的「新生代」和「老生代」

V8 的堆构成

V8 将堆分为了几个不同的区域:

  • 新生区:大多数对象被分配在这里。新生区是一个很小的区域,垃圾回收在这个区域非常频繁,与其他区域相独立。
  • 老生指针区:这里包含大多数可能存在指向其他对象的指针的对象。大多数在新生区存活一段时间之后的对象都会被挪到这里。 老生数据区:这里存放只包含原始数据的对象(这些对象没有指向其他对象的指针)。字符串、封箱的数字以及未封箱的双精度数字数组,在新生区存活一段时间后会被移动到这里。
  • 大对象区:这里存放体积超越其他区大小的对象。每个对象有自己 mmap 产生的内存。垃圾回收器从不移动大对象。 -代码区:代码对象,也就是包含 JIT 之后指令的对象,会被分配到这里。这是唯一拥有执行权限的内存区(不过如果代码对象因过大而放在大对象区,则该大对象所对应的内存也是可执行的。译注:但是大对象内存区本身不是可执行的内存区)。 -Cell 区、属性 Cell 区、Map 区:这些区域存放 Cell、属性 Cell 和 Map,每个区域因为都是存放相同大小的元素,因此内存结构很简单。

分代回收

脚本中,绝大多数对象的生存期很短,只有某些对象的生存期较长。为利用这一特点,V8将堆进行了分代。对象起初会被分配在新生区(通常很小,只有 1-8 MB,具体根据行为来进行启发)。在新生区的内存分配非常容易:我们只需保有一个指向内存区的指针,不断根据新对象的大小对其进行递增即可。当该指针达到了新生区的末尾,就会有一次清理(小周期),清理掉新生区中不活跃的死对象。对于活跃超过 2 个小周期的对象,则需将其移动至老生区。老生区在标记-清除或标记-紧缩(大周期)的过程中进行回收。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。至于足够多到底是多少,则根据老生区自身的大小和程序的动向来定。

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-04-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 垃圾回收
    • JavaScript 内存分配
      • mark and sweep
        • 引用计数(refefence counting)
          • 性能问题
            • 优化内存
            • V8 内存机制
              • V8 的堆构成
                • 分代回收
                • 参考资料
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档