JavaScript垃圾回收机制

一、内存的生命周期

   内存生命周期是指在计算机程序中,内存分配和释放的整个过程,涵盖了内存的创建、使用和最终释放的过程。一般情况下,程序需要使用内存来存储数据和临时计算结果。展开说可以分成分配、初始化、使用、释放、回收。

        总结出来其实就是老生常谈的三点:

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

        在某些情况下,特别是在使用垃圾回收机制的编程语言中,内存的释放不需要显式地由程序员完成。垃圾回收器会自动检测不再需要的内存,并将其回收以供未来使用。


     二、JS中的内存的大致生命周期

  1. 变量声明和分配: 当你在JavaScript中声明一个变量(使用varletconst关键字),系统会为该变量分配内存以存储其值。此时,变量的内存生命周期开始。

  2. 变量初始化: 在变量声明后,你可以选择将一个值赋给它,即进行变量初始化。这将在内存中分配适当的存储空间并将值存储在其中。

  3. 变量使用: 变量被用于存储数据、进行计算等操作。在其作用域内,你可以读取、修改和传递变量的值。

  4. 变量未引用: 当变量不再被引用(即没有任何代码引用该变量),它将变为不可达状态。这是JavaScript内存管理的重要一步,因为不再引用的变量不再需要占用内存。

  5. 垃圾回收: JavaScript引擎中的垃圾回收器会周期性地检测不再被引用的变量和对象,并将其标记为垃圾对象。一旦这些对象被标记为垃圾,垃圾回收器会释放它们占用的内存,使之可用于其他用途。

  6. 内存释放: 一旦垃圾回收器确定某个对象是垃圾,它将回收该对象占用的内存。这个过程可能会被称为“内存释放”。

需要注意的是,JavaScript中的垃圾回收是自动进行的,开发人员不需要显式地释放变量的内存。当变量不再被引用时,垃圾回收器会自动识别并回收不再使用的内存,从而防止内存泄漏。然而,对于一些特殊情况,比如涉及到闭包、循环引用等,开发人员可能需要特别注意以确保内存正常释放。

三、JS的回收机制

1、标记-清除(Mark-and-Sweep):

机制简述:JS中最常见的垃圾回收机制,通过分析对象的"可达性"来决定来是否回收其内存的。

(1)标记

首先,垃圾回收器会从全局对象(通常是window对象)开始,遍历所有可达的对象,包括当前执行上下文中的变量、函数调用栈中的对象以及DOM节点等。

对于每个可达对象,会将其标记为“活动”或“存活”,表示这些对象仍然被引用,不应该被回收。

(2)遍历和递归标记

        对于每个活动对象,垃圾回收器会递归地遍历其属性,标记其中的对象为活动。这确保了所有可以从根对象访问到的对象都被正确标记。

(3)清除

        一旦所有可达对象都被标记,垃圾回收器会遍历整个内存空间,检查未被标记的对象。这些未被标记的对象即为不再被引用的垃圾对象。

        对于每个未被标记的对象,垃圾回收器会将其释放,回收其占用的内存。

(4)内存回收完成

        一旦所有未被标记的垃圾对象被清除,垃圾回收过程完成,内存中就会有更多的可用空间。

优点:高效地检测和回收不再使用的对象,可以处理循环引用。

缺点:①回收后的内存可能会产生不连续的空间碎片,从而影响内存的使用效率。

           ②会造成停顿,即在执行垃圾回收时,程序可能会暂停一段时间,影响用户体验。

2、引用计数(Reference Counting):

机制简述: 每个对象维护一个引用计数,记录有多少个引用指向它。当引用计数变为零时,对象被认为是不再需要的,可以被回收。

(1)引用计数的维护

每个对象在内部维护一个引用计数器,记录当前有多少引用指向该对象。初始情况下,计数器为0。

(2)引用增加

当有一个变量或属性引用指向某个对象时,该对象的引用计数就会增加。

(3)引用减少

当一个引用指向的对象不再被需要时,比如变量被重新赋值、函数执行完毕或对象属性被解除引用,该对象的引用计数就会减少。

(4)判断垃圾对象

垃圾回收器会定期遍历内存中的所有对象,并检查其引用计数。

对于引用计数为0的对象,垃圾回收器认为它们不再被引用,可以被视为垃圾对象。

(5)释放内存

一旦垃圾回收器确定某个对象是垃圾,它会立即释放该对象及其关联的内存空间。

优点:可以快速地回收不再被引用的对象

缺点:①无法处理循环引用:引用计数法无法解决循环引用问题,可能导致内存泄漏。

           ②性能开销大: 需要维护每个对象的引用计数,而且每次引用变化都需要更新计数器,可能造成性能开销。

           ③难以在多线程环境下使用: 在多线程环境中,增加引用和减少引用操作可能会出现并发问题,需要额外的同步措施。

3、分代回收(Generational Garbage Collection):

机制简述: 将内存对象分为不同代(通常是新生代和老生代),新创建的对象首先分配到新生代。新生代通过频繁的垃圾回收快速释放不再使用的对象,而老生代的回收则相对较少。

(1)新生代回收(Young Generation Garbage Collection):

①标记阶段

在新生代的对象中,首先进行标记,标记活动对象。

②复制阶段

将标记的活动对象复制到另一个区域(通常是存活率较低的区域)。

③清除阶段

清除未复制的对象,因为它们不再被引用。

这个过程会频繁执行,因为新生代中的对象往往具有较短的生命周期。生存下来的对象最终会晋升到老生代。

(2)老生代回收(Old Generation Garbage Collection):

老生代的对象往往具有较长的生命周期,垃圾回收发生较少。

对于老生代,采用更复杂的垃圾回收策略,如标记-清除、标记-整理(移动存活对象以填充碎片)、增量回收等。

这些策略目的是在减少停顿时间的同时最大化内存回收效率。

优点:针对不同的对象生命周期进行不同策略的回收,提高性能。

缺点:①需要动态地将对象划分到不同代,可能会增加一些管理开销。

           ②复杂的回收策略可能需要更多的实现和调优。

4、标记-整理(Mark and Compact)

机制简述:用于处理内存中存在碎片的情况。它是一种用于老生代(long-lived objects,存活时间较长的对象)的垃圾回收策略,旨在优化内存的利用和性能。

(1)标记阶段

与标记-清除算法类似,标记阶段从根对象开始,标记所有活动对象。

(2)整理阶段

        在标记阶段完成后,会进行整理操作。整理的目标是将所有活动对象紧凑地放在一起,以消除内存中的碎片。

        对象被依次移动,使它们连续地存放,以便空闲空间形成一个连续的块。

(3)更新引用

        在整理阶段移动对象的过程中,需要更新所有引用到这些对象的地方,以指向它们的新位置。

(4)释放内存

        在整理后,不再存活的对象所占用的内存将会被释放,从而回收内存空间。

优点:

消除碎片: 标记-整理算法通过将活动对象紧凑地放置在一起,消除了内存中的碎片,提高了内存的利用率。

避免内存碎片问题: 内存碎片可能导致大对象无法找到足够连续的内存空间,而标记-整理可以解决这个问题。

缺点:

移动开销: 对象的移动可能会引入一些开销,尤其是在需要更新大量引用的情况下。

②停顿时间: 整理阶段可能会导致较长的停顿时间,因为需要移动对象并更新引用。

5、增量垃圾回收(Incremental Garbage Collection):

机制简述:将垃圾回收过程分成多个小步骤,每次只处理一小部分,然后让程序继续执行。通过交替执行垃圾回收和程序代码,减少长时间的停顿。

(1)初始标记阶段:

        这是一个短暂的暂停,垃圾回收器会标记出从根对象可达的所有对象。这个阶段会短暂停顿应用程序。

(2)并发标记阶段

        在应用程序继续运行的同时,垃圾回收器会并发地标记其他对象。这个阶段会与应用程序交替执行,从而减少长时间的停顿。

(3)重新标记阶段

        在并发标记阶段进行时,应用程序可能会产生新的对象引用。在并发标记结束后,垃圾回收器会重新标记这些新增的引用,确保没有遗漏。

(4)并发清楚阶段

        垃圾回收器会在应用程序继续运行的同时,清除未被标记的垃圾对象,释放内存。

优点:

①减少停顿时间: 增量垃圾回收将垃圾回收过程分解为多个小步骤,应用程序和垃圾回收交替执行,减少了长时间的停顿,提高了用户体验。

②提高响应性: 应用程序能够在垃圾回收过程中继续运行,保持了应用的响应性能。

缺点:

①实现复杂性: 实现增量垃圾回收需要处理并发情况和数据一致性,增加了实现的复杂性。

②性能开销: 垃圾回收和应用程序交替执行,可能会引入一些性能开销,影响应用程序的吞吐量。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值