什么是垃圾回收?
垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
本文主要针对前端js中的垃圾回收机制进行讲解。
我们都知道,JavaScript 中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数……所有这些都需要内存。
当不再需要某样东西时会发生什么? JavaScript 引擎是如何发现并清理它?在解答这个问题前,我们先一起来了解一下堆和栈。
堆区与栈区
在定义变量时,变量名与变量值都是需要存储的,分别对应内存中的两块区域:堆区与栈区。
栈区:变量名与值内存地址的关联关系存放于栈区
堆区:变量值存放于堆区,内存管理回收的则是堆区的内容
如我们定义x = 10, y = 20
当我们执行x=y时,内存中的栈区与堆区变化如下
直接引用与间接引用
直接引用指的是从栈区出发直接引用到的内存地址。间接引用指的是从栈区出发引用到堆区后,再通过进一步引用才能到达的内存地址。如
l2 = [20, 30] // 列表本身被变量名l2直接引用,包含的元素被列表间接引用
x = 10 // 值10被变量名x直接引用
l1 = [x, l2] // 列表本身被变量名l1直接引用,包含的元素被列表间接引用
了解了堆栈和直接引用和间接引用,我们再来看一下可达性
JavaScript 中内存管理的主要概念是可达性,“可达性” 值就是那些以某种方式可访问或可用的值,它们被保证存储在内存中。
1. 有一组基本的固有可达值,由于显而易见的原因无法删除。例如:
本地函数的局部变量和参数
当前嵌套调用链上的其他函数的变量和参数
全局变量
还有一些其他的,内部的
这些值称为根。
2. 如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。
例如,如果局部变量中有对象,并且该对象具有引用另一个对象的属性,则该对象被视为可达性, 它引用的那些也是可以访问的,详细的例子如下。
JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。
接下来,我们通过一个简单的例子来看一下垃圾回收机制
let author = { name: 'dnicyan' }
多引用对象及关联对象的垃圾回收大家可以自行查阅资料,这里就不一一介绍了。
垃圾回收算法
基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:
垃圾回收器获取根并“标记”(记住)它们。
然后它访问并“标记”所有来自它们的引用。
然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
以此类推,直到有未访问的引用(可以从根访问)为止。
除标记的对象外,所有对象都被删除。
demo
我们可以清楚地看到右边有一个“不可到达的块”。现在让我们看看“标记并清除”垃圾回收器如何处理它
第一步标记根
然后标记他们的引用
以及子孙代的引用:
现在进程中不能访问的对象被认为是不可访问的,将被删除:
以上就是标记-清除的垃圾回收机制流程。
JavaScript引擎应用了许多优化,使其运行得更快,并且不影响执行。
优化点:
分代回收——对象分为两组:“新对象”和“旧对象”。许多对象出现,完成它们的工作并迅速结束,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,并且很少接受检查。
增量回收——如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。
空闲时间收集——垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响
了解完垃圾回收机制,下一篇文章我们一起来看看V8引擎的垃圾回收机制是如何实现的?在了解V8垃圾回收机制前,我们一起来看一下上一篇文章遗留的问题吧,****V8引擎在处理js执行过程中,都有哪些优化策略?
1、变量类型不要随意更改,因为V8引擎会默认读取变量初值的参数类型,如sum (x, y){return x + y} ,
sum函数会被认为是热点函数,v8解释器ignition编译后生成的机器代码中,会默认执行sum,传入的参数x y为int整形。之后遇到该函数的调用,会直接执行运行更快的机器代码。如果我们突然传入一个字符串,机器代码将不知如何处理传入的参数,会经过优化重新编译生成新的机器代码。
2、总是以相同的顺序实例化对象属性,以便可以共享隐藏类和随后优化的代码。在实例化之后向对象添加属性将强制隐藏类更改,并放慢为以前隐藏类优化的所有方法。相反,在其构造函数中分配所有对象的属性
3、重复执行相同方法的代码将比仅执行一次(由于内联高速缓存)执行许多不同方法的代码运行得更快。
4、v8用32位的对象和数字。由于它的31位,它使用了一点来知道它是一个对象(flag = 1)还是一个称为SMI(SMall Integer)的整数(flag = 0)。然后,如果一个数字值大于31位,V8会把这个数字装箱,把它变成一个double,并创建一个新的对象,把数字放在里面。尽可能使用31位有符号数字,以避免昂贵的拳击操作成JS对象。
5、总是以相同的顺序实例化对象属性,以便可以共享隐藏类和随后优化的代码。在实例化之后向对象添加属性将强制隐藏类更改,并放慢为以前隐藏类优化的所有方法。相反,在其构造函数中分配所有对象的属性。