Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V8垃圾回收的那些事 #12

Open
YIngChenIt opened this issue Jun 17, 2020 · 0 comments
Open

V8垃圾回收的那些事 #12

YIngChenIt opened this issue Jun 17, 2020 · 0 comments

Comments

@YIngChenIt
Copy link
Owner

YIngChenIt commented Jun 17, 2020

V8垃圾回收

垃圾回收分类

首先js的垃圾回收我们可以分为两类,栈(调用栈)的垃圾回收和堆的垃圾回收,我们分别来看下这2种垃圾回收机制不一样的地方

栈的垃圾回收机制

我们看下面这个代码

function a() {
    function b() {}
    b()
}
a()

代码执行的时候会产生一个调用栈,里面的存储情况大致抽象成这样

[全局执行上下文、函数a的执行上下文、函数b的执行上下文]

假设程序运行到b()的时候,会有一个指针(ESP)指向当前的执行上下文(函数b的执行上下文),当函数b执行完之后需要销毁,也是通过将指针(ESP)下移到上一个执行上下文(函数a的执行上下文),这样如果有新的执行上下文进入调用栈的时候,会直接把函数b的执行上下文直接覆盖,复用了同一块内存。

也就是说栈中无效的内存会被直接覆盖掉

堆的垃圾回收机制

堆中的垃圾回收需要使用 JavaScript 中的垃圾回收器

大致来说V8把堆分为新生代和老生代两个区域,而且分别使用了两个不同的垃圾回收器来高效的实施垃圾回收:

  • 副垃圾回收器,主要负责新生代的垃圾回收。

  • 主垃圾回收器,主要负责老生代的垃圾回收。

新生代垃圾回收

新生代采用GC算法进行垃圾回收,把新生代的内存分为两个区域:对象区域(from)和空闲区域(to),新加入的对象都会被丢到对象区域,当新生代的空间快被占满的时候 GC 就会启动了

在进行垃圾清理的过程中,首先对对象区域的对象进行垃圾标记,副垃圾回收器会把存活的对象复制到空闲区域中,同时会把这些对象有序的排列起来,相当于是完成了内存整理的工作,复制后的空闲区域没有内存碎片了。

完成这步操作之后,对象区域和空闲区域空间互换,等待下一次 GC 算法启动

我们可以发现,每次进行GC算法的时候,都会将对象区域的对象全部复制到空闲区域,这个也是很浪费性能的,所以为了执行效率,新生代的空间一般会给的很小,减少复制所花费的时间

但这也带来了一个问题,新生代的空间这么小,很快就会存在存满的情况,这个时候采用了一种对象晋升策略,经历过两次GC算法还存活的对象,会给移存到老生区

老生代垃圾回收

我们知道老生代一般存储的都是空间比较大,或者经历了两次GC算法还存活的对象,那么对于老生代的垃圾进行GC算法就不科学了,首先复制需要的时间变长了,其次再把空间分为两块也不合理。所以老生代采取的是标记清除法

标记清除法就是从一组根元素开始递归这组根元素,在这个遍历过程中,能够到达的元素为活动对象,到达不了的元素可以判断为非活动对象,也就是垃圾数据。

然后清理掉全部的非活动对象,这个时候内存中就存在大量的内存碎片了,这个时候采取一种标记整理算法,对内存碎片进行整理

垃圾回收的优化

JavaScript 是运行在主线程之上的,因此,一旦执行垃圾回收算法,需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕之后再恢复脚本执行,我们把这个行为称之为 全停顿

假设我们在执行一段js动画脚本,因为垃圾回收执行的关系,导致js动画脚本暂时的挂起,就会出现了卡顿的现象

为了解决全停顿带来的用户体验的问题,V8 团队进行多年的努力,向现有的垃圾回收器添加并行、并发和增量等垃圾回收技术,这些技术主要是从两个方面解决垃圾回收效率的问题:

  • 既然一个大任务执行需要花费很长时间,那么就把它拆分成多个小任务去执行。

  • 将标记、移动对象等任务转移到后台线程进行。这样大大减少主线程暂停的时间,改善页面卡顿的问题

并行回收

竟然js主线程进行垃圾回收的时候会阻塞,那我们可以多开几个线程来辅助主线程的垃圾回收,例如多开一个线上进行标记整理等等

V8 的副垃圾回收器就是采用的这种策略,在执行垃圾回收的过程中同时开启多个辅助线程来对新生代进行垃圾清理的工作,这些线程同时将对象中的数据移动到空闲区域,由于数据地址发生了改变,所以还需要同步更新引用这些对象的指针。

增量回收

老生代中一般存放着比较大的对象,比如说 windowDOM 等,采用并行回收完整的执行垃圾回收依然需要很长时间,这样依然会出现之前提到的动画卡顿的现象,这个时候,V8又引入了增量标记的方式,我们把这种垃圾回收的方式成为增量垃圾回收。

增量垃圾回收就是垃圾收集器将标记工作分成更小的块穿插在主线程的不同任务之间执行。这样,垃圾回收器就没有必要一次执行完整的垃圾回收过程,只要每次执行其中的一小部分工作就可以

并发回收

所谓并发回收,就是指主线程在执行 JavaScript 的过程中,辅助线程能够在后台执行垃圾回收的操作。

参考文献

V8 垃圾回收原来这么简单?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant