js速记--垃圾回收

118 阅读5分钟

一、堆内存与栈内存

1、区别

js中的内存空间分为堆内存和栈内存:

  • 栈内存就是常说的js调用栈,用来储存执行上下文和上下文中的一些基础类型的变量;
  • 堆内存就是来存储一些复杂类型的大片空间,在栈内存中保存着这些复杂类型的指针;

需要注意的是,字符串实际上是保存在堆内存中的,因为栈内存的空间是有限的,而字符串的长度是可以很长的,栈内存可能会无法保存,所以栈内存中保存的是字符串在堆内存中的地址;

2、内存销毁的时机

  • 栈内存中存储的变量,一般在当前执行环境结束,销毁执行上下文对象时会被清空,变量所占用的空间会被回收;
  • 堆内存中存储的变量不会被立即销毁,因为不能确定是否有其他地方对这些变量进行了引用,需要特殊的垃圾回收算法;

二、垃圾回收的几种方式

1、引用计数法

这是一种比较简单的垃圾回收算法,简单说就是记录了每个变量被引用的次数,每当变量被其他对象引用,就会将引用次数增加1,当其他对象断开了对这个变量的引用,引用次数就减1,当执行垃圾回收处理时,检测到某个变量被引用的次数为0时,就可以将这个变量的空间释放。

缺点:无法解决循环引用的问题,当存在循环引用时,这个变量被引用次数永远不会为0

var a = {}
var b = {}
a.b=b
b.a=a

2、标记清除法

标记清除法的核心逻辑就是检测变量的可达性,如果从根节点开始,某个变量不能被访问到,则说明这个变量已经没有被引用,可以销毁。当前所有浏览器采用的都是标记清除法的优化算法。

标记清楚法整个过程分为三个阶段:标记、整理、清除,大致执行过程:

  • 垃圾收集器现将所有变量添加一个初始标记0
  • 从根节点开始,遍历所有变量,将所有访问到的变量,都标记为1
  • 当遍历结束,所有被标记为0的变量,就是需要进行垃圾回收处理的

以上方式的缺点:

  • 剩余内存空间不连续(可能会导致空间浪费和下次内存分配效率低)

优化:就是在标记完后,将标记为1的变量移动到一个连续的内存空间保存,处理完成后再进行清除操作,这样剩下的空间就是一个连续的空间;

三、V8对垃圾回收处理的优化

1、分代式垃圾回收处理

将堆内存分为新生代和老生代两个区域,采用不同的垃圾回收处理方式

(1)新生代

  • 保存时间短的变量
  • 通过Scavenge算法
    • 将内存一分为二,from和to,将from中存活的变量,复制到to中,然后将from中的空间全部销毁,然后from和to角色交换
    • 优点:因为只处理新生代,而新生代中保存的变量较少,所以处理比较快
    • 缺点:有一半空间用来做复制,所以内存使用率降低了

(2)老生代

  • 保存存活时间较长或永驻内存的变量
  • 通过标记清楚算法进行垃圾回收
  • 标记清除算法分类:①标记清除;②标记整理清除
  • 标记清除算法效率较高,默认使用标记清除算法,当内存空间不足以分配晋升时,会使用标记整理清除

(3)晋升

将变量从新生代移到老生代;

  • 新生代中保存的变量经过两次垃圾回收后任然存在的,会被移到老生代
  • to空间不足时,剩余有效变量直接移到老生代

2、并行回收(新生代)

垃圾回收的过程是阻塞页面渲染的,所有当垃圾回收执行时间过长,页面会出现卡顿;所以新生代采用的是并行回收的策略:

简单说就是启用多个线程,并行执行;

3、增量标记与惰性清理(老生代)

老生代中保存的变量往往会占用很大内存,简单的并行回收可能并不能保证很快处理结束,所以采用的是增量标记与惰性清理

(1)增量标记

  • 标记过程分段执行,采用三色标记法;
  • 分段执行:执行一定时间的内存标记后,就停止标记,让js执行一会,等到下个时间片段再继续执行标记;
  • 三色标记法:
    • 采用黑白灰三种标记状态,白色表示未进行标记,灰色标记已进行标记,还未完成,黑色标记已经完成标记
    • 回收器执行标记时,先将变量标记为灰色,当完成当前变量下所有引用的变量的标记后,将当前变量标记为黑色
    • 如果在停止标记后,js执行改变了这个变量,则需要将变量标记状态改为灰色
    • 下次再进入标记流程,会继续检测灰色变量
    • 标记完成后,所有标记为白色的变量,就是不能被访问的变量

(2)惰性清理

在完成标记后,本应该进入清理流程,但是如果当前剩余空间充足,可暂时不清理,继续运行js,下次再清理;或者先清理一部分,剩余标记下次再清理