Contents

垃圾回收

作为目前最流行的 JavaScript 引擎, V8 引擎从出现的那一刻起便广泛受到人们的关注, 我们知道, JavaScript 可以高效地运行在浏览器和 Nodejs 这两大宿主环境中, 也是因为背后有强大的 V8 引擎在为其保驾护航, 甚至成就了 Chrome 在浏览器中的霸主地位. 不得不说, V8 引擎为了追求极致的性能和更好的用户体验, 为我们做了太多太多, 从原始的Full-codegenCrankshaft编译器升级为Ignition解释器和TurboFan编译器的强强组合, 到隐藏类, 内联缓存和HotSpot热点代码收集等一系列强有力的优化策略, V8 引擎正在努力降低整体的内存占用和提升到更高的运行性能.

V8 的来源

V8 的名字来源于汽车的“V 型 8 缸发动机” (V8 发动机) . V8 发动机主要是美国发展起来, 因为马力十足而广为人知. V8 引擎的命名是 Google 向用户展示它是一款强力并且高速的 JavaScript 引擎.

V8 未诞生之前, 早期主流的 JavaScript 引擎是 JavaScriptCore 引擎. JavaScriptCore 是主要服务于 Webkit 浏览器内核, 他们都是由苹果公司开发并开源出来. 据说 Google 是不满意 JavaScriptCore 和 Webkit 的开发速度和运行速度, Google 另起炉灶开发全新的 JavaScript 引擎和浏览器内核引擎, 所以诞生了 V8 和 Chromium 两大引擎, 到现在已经是最受欢迎的浏览器相关软件.

内存生命周期

不管什么程序语言, 内存生命周期基本是一致的:

  1. 分配你所需要的内存
  2. 使用分配到的内存 (读、写)
  3. 不需要时将其释放\归还

所有语言第二部分都是明确的. 第一和第三部分在底层语言中是明确的, 但在像 JavaScript 这些高级语言中, 大部分都是隐含的.

当内存不再需要使用时释放

大多数内存管理的问题都在这个阶段. 在这里最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”. 它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它.

高级语言解释器嵌入了“垃圾回收器”, 它的主要工作是跟踪内存的分配和使用, 以便当分配的内存不再使用时, 自动释放它. 这只能是一个近似的过程, 因为要知道是否仍然需要某块内存是无法判定的 (无法通过某种算法解决) .

可达性

如上文所述自动寻找是否一些内存“不再需要”的问题是无法判定的. 因此, 垃圾回收实现只能有限制的解决一般问题.

JavaScript 中内存管理的主要概念是可达性.

简单地说, “可达性” 值就是那些以某种方式可访问或可用的值, 它们被保证存储在内存中。


有一组基本的固有可达值, 由于显而易见的原因无法删除。例如:

  • 本地函数的局部变量和参数
  • 当前嵌套调用链上的其他函数的变量和参数
  • 全局变量
  • 还有一些其他的, 内部的
  • 这些值称为根。

如果引用或引用链可以从根访问任何其他值, 则认为该值是可访问的。

例如, 如果局部变量中有对象, 并且该对象具有引用另一个对象的属性, 则该对象被视为可达性, 它引用的那些也是可以访问的, 详细的例子如下。

V8 中有一个后台进程称为垃圾回收器, 它监视所有对象, 并删除那些不可访问的对象。

标记-清除算法

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”

举个例子

function marry(man, woman) {
  woman.husban = man;
  man.wife = woman;

  return {
    father: man,
    mother: woman,
  };
}

let family = marry(
  {
    name: 'John',
  },
  {
    name: 'Ann',
  }
);

函数 marry 通过给两个对象彼此提供引用来“联姻”它们,并返回一个包含两个对象的新对象。

产生的内存结构:

/images/garbage-collection-status-1.png

到目前为止,所有对象都是可访问的, 现在让我们删除两个引用:

delete family.father;
delete family.mother.husband;

移除俩个引用后, 对象仍然是可访问的

/images/garbage-collection-status-2.png

如果我们把这两个都删除,那么我们可以看到 John 不再有传入的引用:

/images/garbage-collection-status-3.png

输出引用无关紧要。只有传入的对象才能使对象可访问,因此,John 现在是不可访问的,并将从内存中删除所有不可访问的数据。

等到垃圾回收之后, John 将从内存中删除:

/images/garbage-collection-status-4.png

基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:

  • 垃圾回收器获取根并“标记”(记住)它们。
  • 然后它访问并“标记”所有来自它们的引用。
  • 然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
  • 以此类推,直到有未访问的引用(可以从根访问)为止。
  • 除标记的对象外,所有对象都被删除。

这就是垃圾收集的工作原理。JavaScript 引擎应用了许多优化,使其运行得更快,并且不影响执行。

一些优化:

  • 分代回收——对象分为两组:“新对象”和“旧对象”。 许多对象出现,完成它们的工作并迅速结 ,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,并且很少接受检查。
  • 增量回收——如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。 因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。 这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。
  • 空闲时间收集——垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响。

参考链接

一文搞懂 V8 引擎的垃圾回收

内存管理 - MDN

前端面试:谈谈 JS 垃圾回收机制