十一、JavaScript引擎的垃圾回收机制
引言
在编程语言中,内存管理是一项关键的任务,尤其对于构建大规模和性能敏感的应用程序来说尤为重要。然而,对于JavaScript这种动态语言来说,开发者通常不需要(也无法)直接管理内存,这项任务主要由JavaScript引擎来完成。
这种自动管理的机制让开发者可以更专注于业务逻辑的实现,而不用担心内存泄漏或溢出等问题。但同时,作为开发者,了解JavaScript引擎如何管理内存,如何进行垃圾回收(Garbage Collection,简称GC),也是很有价值的。这种理解可以帮助我们编写出更高效、更具性能的代码,避免可能导致内存问题的代码模式。
- JavaScript内存生命周期
在讨论垃圾回收之前,我们首先需要了解一下JavaScript的内存生命周期,这个过程通常分为三个阶段:
- 分配内存:当声明变量、添加属性、或者调用函数等操作时,JavaScript引擎会分配内存来存储值。例如,当你写let a = 1时,JavaScript引擎会为变量a分配一块内存来存储值1。
- 使用内存:在分配了内存之后,我们可以通过读写操作来使用这块内存。例如,我们可以读取变量a的值,或者改变它的值。
- 释放内存:当内存不再被需要时(例如,变量已经离开了它的作用域),这块内存需要被释放,以便为新的内存分配做出空间。这个过程就是垃圾回收。
2. 垃圾回收
垃圾回收是自动完成的。垃圾收集器会周期性地(或在特定触发条件下)运行,找出不再使用的变量,然后释放其占用的内存。但是,如何确定哪些内存“不再需要”呢?这其实是一个复杂的问题,因为某些内存可能仍然被间接引用,或者可能在将来需要。
因此,垃圾收集器必须使用一种算法来确定哪些内存可以安全地释放。接下来我们将详细介绍两种常见的垃圾回收算法:标记-清除算法和引用计数算法。
1) 标记-清除算法
这是JavaScript中最常用的垃圾回收算法。它的工作原理大致可以分为两个阶段:标记和清除。
在标记阶段,垃圾回收器从一组“根”(root)对象开始,遍历所有从这些根对象可达的对象。可达的对象包括直接引用的对象,以及通过其他可达对象间接引用的对象。所有可达的对象都被标记为“活动的”或“非垃圾的”。
然后,在清除阶段,垃圾回收器会遍历所有的堆内存,清除未被标记的对象。这些未被标记的对象就是我们所说的“垃圾”,它们无法从根对象访问到,因此我们可以安全地假设它们不会再被应用程序使用。
function test() { var x = 123; var y = { a: 1, b: 2 }; // 当函数执行结束时,x 和 y 就离开了环境}test(); // 现在 x 和 y 都是非环境变量,它们占用的内存就可以被垃圾回收器回收
2) 引用计数算法
引用计数是另一种垃圾回收策略。这种策略的基本思想是跟踪每个对象被引用的次数。当声明一个变量并将一个引用类型值赋给该变量时,这个引用类型值的引用次数就是1。如果同一个引用值被赋给另一个变量,引用次数增加1。相反,如果对该值的引用被删除,引用次数减少1。当这个引用次数变成0时,就表示没有任何地方再引用这个值了,因此该值可以被视为“垃圾”并被收集。
然而,引用计数算法有一个著名的问题,那就是循环引用。如果两个对象相互引用,即使它们没有被其他任何对象引用,它们的引用次数也不会是0,因此它们不会被回收,这会导致内存泄漏。为了解决这个问题,现代JavaScript引擎通常会结合使用标记-清除和引用计数两种算法。
function cycleReference() { var obj1 = {}; var obj2 = {}; obj1.prop = obj2; obj2.prop = obj1;}cycleReference();// 在函数执行结束后,obj1 和 obj2 仍然相互引用,但已经离开了环境,无法被引用计数器捕获
带你读《现代Javascript高级教程》十一、JavaScript引擎的垃圾回收机制(2)https://developer.aliyun.com/article/1349620?groupCode=tech_library