什么是GC
GC就是Garbage Collection,程序工作过程中会产生很多垃圾,这些垃圾是程序不用的内存或者是之前用过了,以后不会再用的内存空间,而GC就是负责回收垃圾的。当然也不是所有的语言都会自带GC,比如Java、Python、Javascript等,也有没有GC的语言,比如C、C++等,那这种就需要我们程序员手动管理内存了,相对比较麻烦。
垃圾回收
我们知道写代码的时候创建一个基本类型、对象、函数。。。都是需要占用内存的,但是我们并不关注这些,因为这是引擎为我们分配的,我们不需要显式手动的去分配内存。
但是,你有没有想过,当我们不再需要某个东西的时候会发生什么?javascript引擎又是如何发现并清理他的?
我们举个例子:
let test={
name:"islxk"
};
test=[1,2,3,4,5]
如上所示,我们假设它是一个完整的程序代码
我们知道Javascript的引用数据类型是保存在堆内存中的,然后在栈内存中保存一个对堆内存中实际对象的引用,所以,javascript中对引用数据类型的操作都是操作对象的引用而不是实际的对象。
那么上面代码首先我们先声明了一个变量test,他引用了对象{name:'islxk'}
,接下来我们把这个变量重新赋值了一个数组对象,也就变成了该变量引用了一个数组,那么之前的对象引用关系就没有了,如下图
没有了引用关系,也就是无用的对象,这个时候假如任他搁置,多了内存就会受不了,需要被清理。
用官方一点的话来说,程序的运行需要内存,只要程序提出要求,操作系统或者运行时就必须提供内存,那么对于持续运行的服务进程,必须要及时释放内存,否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
垃圾回收策略
在JS内存管理中有一个概念叫做可达性,就是那些以某种方式可访问或者说可用的值,他们被保证存储在内存中,反之不可访问则需要回收。
至于如何回收,其实就是怎样发现这些不可达的对象(垃圾),并给予清理的问题,js垃圾回收机制的原理说白了就是定期找出那些不再用到的内存,然后释放其内存。
你可能会好奇为什么不是实时找出无用内存并释放呢?其实很简单,实时开销太大了
那么怎样找到其中的垃圾呢?这个流程涉及到了一些算法策略,有很多方式,我们简单介绍两个最常见的:
- 标记清除算法
- 引用计数算法
标记清除算法
标记清除,目前在js引擎里这种算法是最常用的,到目前为止的大多数浏览器的js引擎都在采用标记清除算法,只是各大浏览器厂商还对此算法进行了优化加工,且不同浏览器的js引擎在运行垃圾回收的频率上有所差异。
就像他的名字一样,此算法分为标记和清除两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。
整个标记清除算法大致流程像下面这样: - 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0
- 然后从各个根对象开始遍历,把不是垃圾的节点改为1
- 清理所有标记为0的垃圾,销毁并回收他们所占用的内存空间
- 最后,把所有内存中对象标记修改为0,等待下一轮的垃圾回收
优点:实现简单,打标记无非打和不打两种情况,这使得一位二进制位就可以为其标记,非常简单
缺点:标记清除算法有一个很大的缺点,就是在清除之后剩余之后,剩余的对象内存位置是不变的,也会导致内存空闲空间是不连续的,出现了内存碎片,并且由于剩余空间内存不是一整块,它是由不通过大小内存组成的内存列表,这就牵扯出了内存分配的问题。
假设我们新建对象分配内存时需要大小为size,由于空间内存是间断的、不连续的,则需要对空闲内存列表进行一次单向遍历找出大于等于size的块才能为其分配。
那如何找到合适的块呢?我们可以采取下面三种分配策略
First-fit
,找到大于等于size的块立即返回Best-fit
,遍历整个空闲列表,返回返回大于等于size
的最小块Worst-fit
,遍历整个空闲列表,找到最大的分块,然后切成两部分,一部分size
大小,并将该部分返回
这三个策略中Worst-fit看起来空间利用率最合理,但是实际上会造成更多的小块,形成内存碎片,所以不推荐使用,对于First-fit
和Best-fit
来说,考虑到分配的速度和效率First-fit
是更为明智的选择。
综上所述:标记清除算法或者说策略就有两个很明显的缺点
- 内存碎片化
- 分配速度慢
引用计数算法
引用计数,这其实是早先的一种垃圾回收算法,他把对象是否不再需要
简化定义为对象有没有其他对象引用到它
,如果没有引用指向该对象(零引用),对象将被垃圾回收制回收,目前很少使用这种算法了,因为他的问题很多,不过我们还是需要了解一下
他的策略是跟踪记录每个变量值被使用的次数:
- 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为1
- 如果同一个值又被赋给另一个变量,那么引用数加1
- 如果该变量的值被其他的值覆盖了,则引用次数减1
- 当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,回收空间、垃圾回收器在运行的时候清理掉引用次数为0的值占用的内存