垃圾回收机制:垃圾数据是如何回收的

在数据使用之后,并且不在被需要的数据称为垃圾数据,对这些垃圾数据需要进行回收来释放有限的内存空间。

垃圾回收有分为手动回收自动回收两种策略。

在Javascript中产生的垃圾数据是由垃圾回收器来回收的。

一、调用栈中的数据是如何回收的

在 JavaScript 中,垃圾回收是由浏览器的 JavaScript 引擎负责的。这个引擎会周期性地扫描内存,查找不再被引用的对象,然后释放这些对象占用的内存。当一个函数执行完毕,它的执行上下文就会被标记为不再需要,这时候就可能触发垃圾回收。

js 复制代码
function foo() { 
    var a = { name: '竹合'}; 
    function bar() { 
        var b = { name: '竹合'}; 
    } 
    bar(); 
} 
foo();

在代码执行的时候还有一个记录当前状态的指针称为ESP

当执行到bar()函数的时候,ESP就指向了这个函数,当执行完之后,函数执行流程就到了foo函数,也就需要销毁bar函数的执行上下文,JS就会将ESP下移到foo函数的执行上下文,这个下移过程就是销毁bar函数的执行上下文的过程。

二、堆中的数据是如何回收的

垃圾回收机制主要有两种策略:

1. 标记清除(Mark and Sweep):

这是 JavaScript 最常用的垃圾回收策略之一。它的基本原理是在执行上下文中标记那些仍然被引用的变量,然后清除未被标记的变量。这通常是通过追踪从全局对象开始的引用链来完成的。

2. 引用计数:

这种策略会为每个值都维护一个引用计数,当这个值的引用计数变为零时,说明它不再被引用,可以被回收。然而,引用计数策略容易出现循环引用的问题,因为如果两个对象互相引用,它们的引用计数永远不会变为零。

JavaScript 引擎中的垃圾回收器会在适当的时候自动运行。具体的触发时机和实现方式可能因浏览器引擎而异。一般来说,垃圾回收器会在空闲时执行,确保不会对页面性能产生明显的影响。

大部分现代浏览器主要采用标记-清除(Mark and Sweep) 作为其主要的垃圾回收策略。

标记清除的基本原理是通过标记那些仍然被引用的变量,然后清除未被标记的变量。垃圾回收器会从全局对象开始,通过引用链遍历对象,标记那些仍然被引用的对象,然后清除那些未被标记的对象。这个过程确保了内存中只保留了仍然可达的对象,不再被引用的对象就可以被安全地回收。

JavaScript 引擎中的具体垃圾回收实现可能会有一些优化和改进,以提高性能和减少对页面的影响。例如,V8 引擎(Chrome 使用的 JavaScript 引擎)使用了一种称为增量标记的技术,以减小垃圾回收的停顿时间。其他引擎也可能采用类似的优化策略。

V8 引擎中增量标记的基本工作流程(主要用在老生代中):

1. 初始标记阶段(Initial Marking):

  • 在这个阶段,V8 首先执行一次快速的初始标记,标记那些直接与根对象关联的对象。这个阶段的目标是尽快完成,以减小初始的停顿时间。

2. 并发标记阶段(Concurrent Marking):

  • 在并发标记阶段,V8 引擎启动后台线程,该线程负责继续标记其余的对象,但不会阻塞主线程执行 JavaScript 代码。这样,应用程序可以在并发标记的同时继续运行,减小了对用户体验的影响。

3. 重标记阶段(Re-Marking):

  • 在并发标记完成后,V8 需要执行最后的重标记步骤,确保在并发标记期间发生变化的对象也被正确标记。这一步会导致短暂的停顿。

4. 清理阶段(Clean-Up):

  • 清理阶段用于清理不再被引用的对象,释放它们占用的内存。这一阶段通常也是并发执行的。

回收堆中的垃圾数据就需要用到JS中的垃圾回收器。

代际假说(Generational Hypothesis) 是垃圾回收领域的一种理论,它提出了一个观点:大多数对象在内存中存在的时间很短,而只有一小部分对象会长时间存活。基于这个观点,代际假说提出了一种优化垃圾回收的方法,即将对象分为不同的代际,并根据它们的存活时间采用不同的回收策略。

代际假说的核心思想包括以下两点:

1. 新生代(Young Generation):

大多数对象在被创建后很短时间内就会被销毁,而只有一小部分对象会存活更长的时间。因此,将对象划分为新生代和老生代两个部分,新生代包含大部分短时间存活的对象。

2. 分代回收策略:

针对不同代际的对象采用不同的回收策略。新生代的对象使用一种较为频繁但简单的回收算法,例如复制算法(Copying Algorithm) ,以迅速清除短寿命的对象。而老生代的对象使用更复杂的回收算法,例如标记-清除(Mark and Sweep)算法,以处理存活时间较长的对象。

代际假说的优势在于,通过将对象分为新生代老生代,并使用不同的回收策略,可以更有效地提高垃圾回收的性能。大部分对象很快就会被清理,只有一小部分对象需要经历更复杂的回收过程。这使得垃圾回收可以更快地完成,减小了对应用性能的影响。

在V8引擎中会把堆分为新生代和老生代两个区域。新生代用来存放生存时间短的对象(通常支持1~8M的容量),老生代用来存放生存时间久的对象。

V8引擎也分别使用两个不同的垃圾回收器:

  1. 副垃圾回收器,负责回收新生代的垃圾。

  2. 主垃圾回收器,负责回收老生代的垃圾。

1. 垃圾回收器的工作流程

两种类型的垃圾回收器共用一套共同的流程。

  1. 首先是标记空间中的活动对象(还在使用的对象)和非活动对象(可以进行垃圾回收的对象)。

  2. 然后回收非活动对象所占据的内存。在所有标记完成之后,统一清理内存中的所有被标记可以回收的对象。

  3. 最后是内存整理:频繁回收对象之后,内存中会存在大量不连续空间(内存碎片)。当内存中出现了大量碎片,如果需要分配大量连续内存,就有可能出现内存空间不足的情况。

2. 副垃圾回收器

新生代用Scavenge算法来处理,也就是将新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域。

新加入的对象都会存放到对象区域,当对象区域快被写满的时候就会执行一次垃圾清理操作。

在垃圾回收过程中,首先要对对象区域中的垃圾做标记,标记完成之后进入垃圾清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域中,同时还会把这些对象有序的排列起来,也就完成了内存整理的操作,完成复制之后空闲区域和对象区域进行翻转,这样就完成了垃圾对象的回收操作。

V8引擎采用了对象晋升策略,经过两次垃圾回收还存活的对象会被移动到老生区中。

3. 主垃圾回收器

主垃圾回收器存放的对象的特点是:1.对象占用空间大;2.对象存活时间长。

也就是采用复制对象的策略会会花费比较多的时间,所以主垃圾回收器采用的策略是标记-清除(Mark-Sweep)标记-整理(Mark-Compact) 两个算法进行垃圾回收的。

1. 标记-清除算法(Mark-Sweep):

a. 标记阶段:

  1. 初始标记(Initial Marking):

垃圾回收器从根对象开始,标记所有能够直接或间接访问到的对象。这个阶段的目标是快速标记那些与根对象直接关联的对象。

  1. 并发标记(Concurrent Marking):

在初始标记完成后,V8 引擎启动后台线程进行并发标记。这个阶段并发地标记那些与根对象间接关联的对象,不阻塞主线程执行 JavaScript 代码。

  1. 重标记(Re-Marking):

在并发标记完成后,需要执行最后的重标记步骤,确保在并发标记期间发生变化的对象也被正确标记。这一步会导致短暂的停顿。

b. 清除阶段:

  1. 清除无标记对象(Clear Unmarked)

2. 标记-整理算法(Mark-Compact):

在标记-清除算法的基础上,标记-整理算法引入了整理阶段,以减小内存碎片。

a. 整理阶段:

  1. 整理(Compacting Live Objects):

3. 增量标记(Incremental Marking):

V8 引擎的老生代垃圾回收器还包括增量标记技术。增量标记将标记阶段分解成多个小步骤,允许在标记过程中与 JavaScript 应用交替执行。这降低了垃圾回收导致的停顿时间,提高了应用的响应性。

相关推荐
王哲晓9 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_41112 分钟前
无网络安装ionic和运行
前端·npm
理想不理想v13 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云23 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:1379712058725 分钟前
web端手机录音
前端
齐 飞31 分钟前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹1 小时前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
aPurpleBerry1 小时前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子2 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html