垃圾回收机制
介绍
垃圾回收是指将无用的内存释放的机制。
在JS中基本类型是使用栈存储的,而引用类型的值是使用堆存储的,引用地址依然是使用栈存储。所以当我们将一个引用类型的值设置为null时,栈存储的地址更改了,但堆所占有的内存也需要清除,而清除的方式就是通过垃圾回收机制进行释放内存。
引用计数
计算一个对象被引用的次数进行垃圾回收。对于引用次数为0的对象进行销毁。
这个方法会导致相互引用的变量永远不会被销毁。
标记清除
对于进入执行环境的变量打上标记,离开执行环境的变量则清除标记。清除时,将所有无标记的变量销毁、回收内存。
标记清除实现简单,大部分浏览器的垃圾回收机制都是基于这个方法进行优化。
简单地将变量进行销毁会带来一个问题:剩余的对象内存位置没有变化,只有被销毁的变量内存空闲出来,内存占用不连贯。所以在标记清除之后还需要继续标记整理(Mark-Compact),将剩余占有的内存整理到一起。
V8引擎对标记清除的优化
标记清除需要定期遍历对象,确认是否需要销毁。V8区别开新产生的对象与旧的对象,对他们进行不同的处理。
新生代
新生代用于存储新产生的对象,一般只有1-8M。
堆内存分为两个区域,一个是使用区,一个是空闲区,对象都会存放到使用区中。当使用区满时就会执行一次垃圾回收。
通过标记确认在执行环境内的对象将被复制在空闲区中,然后会清空使用区。这时的空闲区就变成了使用区,使用区变成了空闲区。
多次复制后依然存在的对象就会移动到老生代内存中。或者在复制过程中,占有的内存超过了限制(25%)也会直接移动至老生代。
老生代
老生代直接使用了标记清除进行垃圾回收,标记阶段从一组根元素开始,遍历所有元素增加标记。再对无标记的元素进行销毁。
并行回收
由于js是单线程,所以为了防止垃圾回收阻塞js执行,导致页面卡顿。通过多进程进行并发处理垃圾回收。
增量标记
将垃圾回收的过程细分化,每次进行一部分。
通过三色标记法,可以确认哪个对象还在检测中。
- 白色代表未被标记
- 灰色代表自身被标记,但内部还有未被标记的变量
- 黑色代表自身与内部变量都被标记
这样在执行到中途时就可以暂停,等下次再继续垃圾回收。如果有灰色的节点,表示还需要继续遍历,不存在灰色节点的时候就可以直接进行销毁阶段。
当黑色节点内部存在白色节点,这个白色节点会被修改为灰色。保证当前节点不会在恢复后被销毁。这种方法叫写屏障 (Write-barrier)。
惰性清理(Lazy Sweeping)
在销毁阶段也可以分批继续销毁,防止长时间阻塞。