JavaScript

JavaScript 知识量:26 - 101 - 483

3.3 垃圾回收><

内存管理- 3.3.1 -

JavaScript的内存管理主要是自动的,由JavaScript引擎来负责。JavaScript没有直接提供手动内存管理的机制,如申请内存和释放内存等。但了解其内存管理机制能帮助更好地写出高效的代码。

JavaScript的内存主要分为堆(Heap)和栈(Stack)两种。当函数被调用时,函数中的变量会被存储在栈中。当函数执行完毕后,这些变量会被自动从栈中清除。

而当对象被创建时,它们会被存储在堆中。例如,当创建一个对象,如 let obj = {};,这个对象就会被存储在堆中。由于JavaScript的垃圾回收机制,当一个对象不再被引用时,这个对象就会成为垃圾,垃圾回收器会在适当的时机将其从堆中清除,释放其占用的内存。

这个“适当的时机”就是垃圾回收器进行垃圾回收的时候。垃圾回收器会定期运行,找出所有不再被引用的对象,将它们清理掉,以此来自动管理内存。这个过程可能会涉及到两个阶段:

标记清除(Mark-Sweep):这是最基本的垃圾回收算法,分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾回收器会从一些根对象开始,遍历所有对象,标记出所有还在被引用的对象。在清除阶段,垃圾回收器会清理掉所有没有被标记的对象。

分代收集(Generational Collection):在现代JavaScript引擎中,通常会使用分代收集来更高效地管理内存。这种策略基于一个观察:大多数对象很快就会失去引用,而少数对象会持续被引用。因此,将内存分为年轻代(存储新创建的对象)和老年代(存储长时间存在的对象)两个或多个世代可以更有效地进行垃圾回收。当年轻代空间不足时,会触发Minor GC,对年轻代进行标记和清理;当老年代空间不足时,会触发Major GC或Full GC,对老年代进行标记和清理。

标记清理- 3.3.2 -

标记清理(mark-and-sweep)是JavaScript中最常用的垃圾回收策略。它的工作原理可以简单概括为两个阶段:

  1. 标记阶段:从根(root)开始,遍历所有可达的(reachable)对象,把它们标记为活动的。这个过程也被称为根可达性分析(root traversal)或宽向量扫描(wide vector scan)。

  2. 清扫阶段:遍历整个堆(heap),回收所有未被标记(即不可达)的对象的内存。这个阶段也叫做深度清除(depth-first cleanup)或窄向量扫描(narrow vector scan)。

标记-清扫算法是最常用的垃圾回收策略,因为它可以有效地管理内存,找出并清除不再需要的对象,从而防止内存泄漏。然而,它也有一些缺点,比如可能会导致较长的暂停时间(被称为"垃圾回收暂停"),并且可能会优先清除那些最近最少使用的对象,而不是那些占用大量内存的对象。为了解决这些问题,许多现代的JavaScript引擎都采用了更复杂的垃圾回收策略,比如分代收集(generational collection)、增量标记(incremental marking)和并发清理(concurrent sweeping)等。

分代收集- 3.3.3 -

分代收集(Generational Collection)是一种针对年轻代的垃圾收集算法,它是引用计数和标记清除算法的结合。该算法将系统中的所有对象分为两个或多个世代(Generation),其中年轻代(Young Generation)存储新创建的对象,而老年代(Old Generation)存储长时间存在的对象。

分代收集算法的核心思想是:大多数对象很快就会失去引用,因此它们不需要存储在老年代中。只有那些长时间存在的对象才需要被移动到老年代。这种思想可以显著减少垃圾收集的开销,因为老年代中的对象数量通常比年轻代中的对象数量少得多。

在分代收集算法中,年轻代通常被进一步细分为三个部分:Eden区、Survivor区和老年代。当一个新对象被创建时,它将被分配到Eden区。当Eden区中的对象数量增加到一定阈值时,会触发一次Minor GC(Garbage Collection),将Eden区中仍然被引用的对象复制到Survivor区。Survivor区满后,被引用的对象将被复制到下一个Survivor区或老年代。

老年代中的对象通常包括长时间存在的对象,例如全局变量或长生命周期的对象。老年代的垃圾收集被称为Major GC或Full GC,它的开销通常比Minor GC大得多,因为它需要检查的对象数量更多。

分代收集算法通过将系统中的对象进行分类并对不同世代进行不同的垃圾收集策略,实现了高效的内存管理。它是一种广泛应用于现代Java虚拟机(JVM)和JavaScript引擎的垃圾收集算法。

增量标记- 3.3.4 -

增量标记(Incremental Marking)是一种垃圾回收算法中的技术,它是在标记-清除(Mark-Sweep)算法的基础上发展而来的。在标记-清除算法中,垃圾收集器会先标记出所有活动的对象,然后清除掉未被标记的对象。这种方式在处理大量内存时可能会引发效率问题,因为它需要暂停应用程序并一次性处理所有的内存。

增量标记算法是为了改进标记-清除算法的这个问题。在增量标记算法中,垃圾收集器不会一次性标记所有的活动对象,而是将标记过程分散到多个阶段中。每个阶段都会标记一部分对象,这样就可以在应用程序运行时逐步地进行垃圾回收,而不需要一次性暂停应用程序。

具体来说,增量标记算法可能会采用以下步骤:

  1. 初始阶段:应用程序开始运行,对象开始被创建和使用。

  2. 标记阶段:当应用程序暂停时,垃圾收集器开始执行标记过程。这个过程分为多个增量阶段。在每个增量阶段中,垃圾收集器会标记一部分对象,而不是全部对象。这个部分的确定可以基于各种策略,例如基于对象的年龄(即“分代收集”策略)或其他引用属性。

  3. 清除阶段:在所有的对象都被标记之后,垃圾收集器会清除未被标记的对象。由于只清除未被标记的对象,因此这个过程可以在应用程序运行时进行,而不需要暂停应用程序。

  4. 结束阶段:垃圾收集过程结束,应用程序可以继续运行。

增量标记算法的优点是可以逐步进行垃圾回收,不需要一次性暂停应用程序。缺点是可能会导致更复杂的算法实现,而且可能会增加垃圾回收的开销。这种算法通常会与其他垃圾回收策略结合使用,例如分代收集或复制收集,以实现更高效的内存管理。

并发清理- 3.3.5 -

JavaScript的并发清理(Concurrent Sweeping)是垃圾回收机制中的一种技术,主要应用在分代收集(Generational Collection)策略中。

并发清理是为了提高垃圾回收的效率而设计的。在传统的标记-清除(Mark-Sweep)或复制(Copying)垃圾回收算法中,清理阶段需要暂停应用程序(Stop-The-World),即所谓的“停顿”。这会影响应用程序的性能,因为在这段时间内,JavaScript无法执行任何其他任务。

并发清理允许垃圾回收器在后台与应用程序同时运行。在清理阶段,虽然垃圾回收器会占用一部分系统资源,但不会完全阻止应用程序运行。这意味着应用程序可以在垃圾回收器进行清理阶段的同时继续执行任务,从而减少了停顿时间,提高了整体性能。

并发清理的实现方式可能因JavaScript引擎而异,但通常涉及以下步骤:

  1. 标记阶段:与传统的分代收集一样,垃圾回收器首先对年轻代的对象进行标记。这个阶段可能需要暂停应用程序,因为它需要跟踪和标记所有可达的对象。

  2. 并发清理阶段:一旦标记阶段完成,垃圾回收器将进入并发清理阶段。在这个阶段,垃圾回收器会遍历老年代的对象,将未被标记的对象进行清理。由于这个阶段不需要暂停应用程序,因此可以与应用程序同时运行。

  3. 重新整理:在并发清理阶段完成后,垃圾回收器可能会对老年代进行重新整理(Compaction),以减少内存碎片和提高内存利用率。这个阶段可能需要暂停应用程序,具体取决于实现细节。

通过将清理阶段与应用程序并发运行,并发清理技术可以减少垃圾回收对应用程序性能的影响。然而,它可能需要更复杂的实现和额外的系统资源,因此在实际应用中需要根据具体场景进行权衡和优化。