认识V8引擎
- V8是一款主流的JavaScript执行引擎
- V8采用即时编译(把源码直接翻译成可以立刻执行的机器码)
- V8内存设限(64位操作系统不超过1.5G,对于32位不超过800M)
V8垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代、老生代存储区
- 针对不同对象采用不同的GC算法
V8中常用的GC算法
- 分代回收
- 空间复制
- 标记清除
- 标记整理
- 标记增量
V8如何回收新生代对象
首先来看看V8的内存分配示例图:
- V8的内存空间一分为二
- 小空间用于存储新生代对象(32M:64位 | 16M:32位)
- 新生代指的是存活时间较短的对象(比如说函数作用域里的变量)
新生代对象回收实现
- 回收过程采用复制算法+标记整理
- 新生代内存区分位两个等大小空间
- 使用空间为From,空闲空间位To
- 活动对象存储于From空间
- 标记整理后将活动对象拷贝至To
- From空间清除,释放掉整个空间
- From与To交换空间,使得To成为From空间,From空间成为新的To空间
这个过程中,只复制了活动的对象,而非活动的对象被忽略,从而实现了对新生代对象的回收。由于新生代对象的特性是短命,这种复制算法比较适合,因为大多数新生代对象很快就会变成垃圾。
回收细节说明
- 拷贝过程中可能出现晋升
- 晋升就是将新生代对象移动到老生代
- 一轮GC还存活的新生代对象就需要晋升
- 如果拷贝过程中,To空间的使用率超过25%,就要将这次的活动对象都移动老生代存储区存放
这里要额外说明:To空间的使用率如果超过一定限制,在将来进行空间交换,变为使用状态时,新进来的对象空间就不够用了,这就是要有使用率限制的原因。
V8如何回收老生代对象
- 在上面的图里,我们可以知道老生代对象是存放在右侧的老生代区域
- 64位操作系统1.4G,32位操作系统700M
- 老生代对象就是指存活时间较长的对象(比如说,全局对象下存放的变量,闭包的一些变量数据)
老生代对象回收实现
- 主要采用标记清除,标记整理,增量标记算法
- 首先使用标记清除完成垃圾空间回收
- 采用标记整理进行空间优化(如果发现新生代的内容往老生代区域移动,并且老生代空间不足以存放新生代的移动对象,也就是晋升,这个情况就会使用标记整理来处理碎片化的空间)
- 采用增量标记进行效率优化
细节对比
- 新生代区域垃圾回收使用空间换时间。因为新生代区域的空间很小,采用了复制算法,每时每刻内部都有空闲区域。分出来的空间又会更小,针对于时间上的显著提升,空间上的浪费就显得不是那么重要了。
- 老生代区域垃圾回收不适合复制算法,因为老生代的大小很大,这就代表了有一半的区域无法在复制算法的执行过程中被使用,这个空间的容量就有几百兆左右,另外,老生代区域中的内容是比较多的,所以,在复制的过程中,我们消耗的时间是比较多的。
标记增量如何优化垃圾回收
有一点要明白:当垃圾回收机制开始工作的时候,他是会阻塞JavaScript程序执行的
在垃圾回收机制开始工作的时候,JavaScript就不会再继续执行了,而是等待遍历对象,这就是空档期。
所谓的标记增量就是,把整个垃圾回收操作分解为多个小部分,来组合着完成整个回收,替代之前一口气做完的垃圾回收操作。
好处就是:可以实现垃圾回收和程序执行交替的完成。这样带来的时间消耗会更加的合理一点。
最后的清除操作是没法分解的,就要让JavaScript停止运行了。
整个V8的最大的垃圾回收,达到了1.5G的时候,采用非增量标记的垃圾回收机制时间也不超过1秒钟。所以这段的间断间隔是合理的,最大限度的把很长的停顿时间直接拆分成了更小段,这就会对用户的体验有更好的优化