Skip to main content

垃圾回收

Javascript是使用垃圾回收的语言,也就是说执行环境负责在代码执行的时候管理内存。

Javascript通过自动内存管理实现分配内存闲置资源回收

v8 引擎 的内存限制

在浏览器中通过Javascript使用内存的时候,就会发现只能使用部分内存(64 位系统 1.4G,32 位系统 0.7G)。这也就意味着将无法直接操作一些大内存对象

V8 引擎 为何要内存限制

  • JavaScript 最初只运行在浏览器环境,几乎不会遇到大量使用内存的场景,所以对于网页来说,V8 的限制已经绰绰有余
  • V8 在执行垃圾回收时会阻塞 JavaScript 应用逻辑,直到垃圾回收结束再重新执行 JavaScript 应用逻辑。
  • V8 的堆内存为 1.5GB,V8 做一次小的垃圾回收需要 50ms 以上,做一次非增量式的垃圾回收甚至要 1 秒以上。这样浏览器将在 1s 内失去对用户的响应,造成假死现象

垃圾回收基本思路

  • 确定哪个变量不会在使用,然后释放它占用的内存。
  • 垃圾回收这个过程是周期性的,所以垃圾回收会每隔一段时间就会自动运行。

标记清理

Javascript常用的垃圾回收策略是标记清理(mark-and-sweep)

工作原理:

  • 垃圾回收程序运行的时候, 会标记内存中存储的所有变量,假设内存中所有对象都是垃圾,全标记为 0
  • 然后从各个根对象开始遍历, 把不是垃圾的节点标记为 1'
  • 剩下的就是变量就是待删除的节点了。
  • 清理所有标记为 0 的垃圾,销毁并回收它们所占用的内存空间。

优点

  • 简单易懂,实施较为直接。
  • 可以有效处理循环引用的问题,因为它通过根对象遍历所有可达对象。

缺点

  • 可能导致内存碎片化,因为清除阶段不会压缩内存空间。
  • 整个堆的遍历可能导致性能下降,特别是在大规模内存的情况下。

引用计数

另一个没那么常用的垃圾回收策略是引用计数(reference-counting)

主要思路是对每个值都记录它被引用的次数

工作原理

引用计数定义:

  • 每个对象都有一个引用计数器,表示有多少个引用指向该对象。

引用增加:

  • 当有新的引用指向该对象时,引用计数会增加
let obj1 = { name: "example" }; // obj1 的引用计数为 1
let obj2 = obj1; // obj2 也指向 obj1,引用计数增加到 2

引用减少:

  • 引用被解除超出作用域时,引用计数会减少。
let obj1 = { name: "example" }; // obj1 的引用计数为 1
let obj2 = obj1; // obj2 也指向 obj1,引用计数增加到 2
obj2 = null; //obj1 的引用计数减少到 1

回收机制:

  • 引用计数降为 0 时,说明没有任何引用指向该对象,此时可以安全地释放内存

优点

  • 实时性:引用计数可以即时回收内存,避免了长时间占用内存。
  • 简单性:实现相对简单,易于理解。

缺点

  • 循环引用问题:

    如果两个对象互相引用,即使没有其他引用指向它们,它们的引用计数也不会降为 0,导致内存泄漏。

function test() {
let obj1 = {};
let obj2 = {};

obj1.b = obj2;
obj2.a = obj1;

// 这时,即使 obj1 和 obj2 超出了作用域,它们的引用计数仍然为 1,无法被回收。
}
  • 性能开销:

    每次引用变化都需要更新计数器,增加了开销,尤其是在频繁创建和销毁对象的情况下