关于前端:现代JavaScript高级教程JavaScript引擎的垃圾回收机制

5次阅读

共计 3060 个字符,预计需要花费 8 分钟才能阅读完成。

点击在线浏览,体验更好 链接
古代 JavaScript 高级小册 链接
深入浅出 Dart 链接
古代 TypeScript 高级小册 链接

JavaScript 引擎的垃圾回收机制

1. 引言

在编程语言中,内存治理是一项要害的工作,尤其对于构建大规模和性能敏感的应用程序来说尤为重要。然而,对于 JavaScript 这种动静语言来说,开发者通常不须要(也无奈)间接治理内存,这项工作次要由 JavaScript 引擎来实现。

这种主动治理的机制让开发者能够更专一于业务逻辑的实现,而不必放心内存透露或溢出等问题。但同时,作为开发者,理解 JavaScript 引擎如何治理内存,如何进行垃圾回收(Garbage Collection,简称 GC),也是很有价值的。这种了解能够帮忙咱们编写出更高效、更具性能的代码,防止可能导致内存问题的代码模式。

2. JavaScript 内存生命周期

在探讨垃圾回收之前,咱们首先须要理解一下 JavaScript 的内存生命周期,这个过程通常分为三个阶段:

  1. 分配内存 :当申明变量、增加属性、或者调用函数等操作时,JavaScript 引擎会分配内存来存储值。例如,当你写let a = 1 时,JavaScript 引擎会为变量 a 调配一块内存来存储值1
  2. 应用内存 :在调配了内存之后,咱们能够通过读写操作来应用这块内存。例如,咱们能够读取变量a 的值,或者扭转它的值。
  3. 开释内存:当内存不再被须要时(例如,变量曾经来到了它的作用域),这块内存须要被开释,以便为新的内存调配做出空间。这个过程就是垃圾回收。

3. 垃圾回收

垃圾回收是主动实现的。垃圾收集器会周期性地(或在特定触发条件下)运行,找出不再应用的变量,而后开释其占用的内存。然而,如何确定哪些内存“不再须要”呢?这其实是一个简单的问题,因为某些内存可能依然被间接援用,或者可能在未来须要。因而,垃圾收集器必须应用一种算法来确定哪些内存能够平安地开释。接下来咱们将具体介绍两种常见的垃圾回收算法:标记 - 革除算法和援用计数算法。

3.1. 标记 - 革除算法

这是 JavaScript 中最罕用的垃圾回收算法。它的工作原理大抵能够分为两个阶段:标记和革除。

在标记阶段,垃圾回收器从一组“根”(root)对象开始,遍历所有从这些根对象可达的对象。可达的对象包含间接援用的对象,以及通过其余可达对象间接援用的对象。所有可达的对象都被标记为“流动的”或“非垃圾的”。

而后,在革除阶段,垃圾回收器会遍历所有的堆内存,革除未被标记的对象。这些未被标记的对象就是咱们所说的“垃圾”,它们无奈从根对象拜访到,因而咱们能够平安地假如它们不会再被应用程序应用。

function test() {
    var x = 123;
    var y = {a: 1, b: 2};
    // 当函数执行完结时,x 和 y 就来到了环境
}
test();
// 当初 x 和 y 都是非环境变量,它们占用的内存就能够被垃圾回收器回收

3.2. 援用计数算法

援用计数是另一种垃圾回收策略。这种策略的根本思维是跟踪每个对象被援用的次数。当申明一个变量并将一个援用类型值赋给该变量时,这个援用类型值的援用次数就是 1。如果同一个援用值被赋给另一个变量,援用次数减少 1。相同,如果对该值的援用被删除,援用次数缩小 1。当这个援用次数变成 0 时,就示意没有任何中央再援用这个值了,因而该值能够被视为“垃圾”并被收集。

然而,援用计数算法有一个驰名的问题,那就是循环援用。如果两个对象互相援用,即便它们没有被其余任何对象援用,它们的援用次数也不会是 0,因而它们不会被回收,这会导致内存透露。为了解决这个问题,古代 JavaScript 引擎通常会联合应用标记 - 革除和援用计数两种算法。

function cycleReference() {var obj1 = {};
    var obj2 = {};
    obj1.prop = obj2;
    obj2.prop = obj1;
}
cycleReference();
// 在函数执行完结后,obj1 和 obj2 依然互相援用,但曾经来到了环境,无奈被援用计数器捕捉

4. JavaScript 引擎的垃圾回收优化策略

古代 JavaScript 引擎不仅实现了上述的根底垃圾回收算法,而且引入了一些优化策略,以进步垃圾回收的效率并减小对性能的影响。

4.1. 分代收集

大部分的 JavaScript 对象在创立后很快就会死亡,而那些能活下来的对象,通常能活很久。这给了 JavaScript 引擎一个优化垃圾收集的思路。它把内存堆分为两个

区域:新生代和老生代。新生代寄存的是生存工夫短的对象,老生代寄存的是生存工夫长的对象。

对新生代的垃圾回收采纳 Scavenge 算法,它将新生代的空间一分为二,一个为应用空间(From),一个为闲暇空间(To)。新对象总是被调配到 From 空间,当 From 空间快被应用完时,就会触发垃圾回收过程。回收过程中,存活的对象将会被复制到 To 空间,同时 From 和 To 空间的角色会对调,也就是原来的 To 空间变成新的 From 空间。这个过程称为新生代的降职策略。

而老生代的对象数量个别较多且存活工夫较长,如果还应用下面的 Scavenge 算法就会占用较多的 CPU,因而老生代采纳了标记 - 革除和标记 - 整顿算法。

4.2. 提早革除和增量标记

为了减小垃圾回收过程对应用程序性能的影响,JavaScript 引擎采纳了“提早革除”(Lazy Sweeping)和“增量标记”(Incremental Marking)两种策略。

“提早革除”是指,在标记 - 革除算法中,垃圾回收器并不是在标记完对象之后立刻革除,而是将革除操作提早到应用程序闲暇时进行。

“增量标记”则是将一次残缺的标记过程合成为几个局部,每个局部只标记一部分对象。这样,垃圾回收器能够在运行一小段时间后,暂停一会儿,让出 CPU 给应用程序,而后再运行一小段时间,如此重复,直到标记所有对象。这种形式能够让垃圾回收和应用程序交替运行,减小了垃圾回收对应用程序性能的影响。

4.3 JavaScript 代码优化和垃圾回收

理解了垃圾回收的基本概念和机制后,咱们能够通过优化 JavaScript 代码来缩小垃圾回收的压力,进步程序的性能。以下是一些根本的策略:

1. 局部变量和立刻开释内存

应用局部变量而不是全局变量能够更快地开释内存。这是因为局部变量的生命周期通常比全局变量短,一旦来到了它的环境(例如:函数执行完结),局部变量就能够被标记为垃圾回收。

function test() {
    var local = "I'm a local variable";
    // 当函数执行完结后,local 就来到了环境,能够被垃圾回收
}
test();
2. 解除对象援用

当你不再须要一个对象时,应该解除对它的援用。这样,垃圾回收器在下一次运行时就能够回收这个对象。

var obj = {prop: "I'm an object"};
obj = null; // 当初,obj 能够被垃圾回收
3. 防止长生命周期的援用

长生命周期的援用(例如:全局变量或 DOM 援用)会阻止垃圾回收器回收它们所援用的对象。因而,应该尽量避免应用长生命周期的援用,或者在不再须要它们时及时解除援用。

在了解了 JavaScript 的垃圾回收机制和如何优化代码以加重垃圾回收压力之后,咱们能够写出更高效、更牢靠的代码,从而进步用户体验,升高零碎负载。

5. 总结

JavaScript 的垃圾回收机制是一个简单且精妙的零碎,它能主动治理内存,让开发者能够专一于实现业务逻辑。尽管大多数时候咱们不须要关怀垃圾回收的具体过程,然而理解其工作原理,能够帮忙咱们编写出更高效、更具性能的代码,防止可能导致内存问题的代码模式。

正文完
 0