V8内存管理及垃圾回收机制

45次阅读

共计 1969 个字符,预计需要花费 5 分钟才能阅读完成。

JavaScript 引擎的内存空间次要分为栈和堆。

栈是长期存储空间,次要存储局部变量和函数调用。

根本类型数据(Number, Boolean, String, Null, Undefined, Symbol, BigInt)保留在在栈内存中。
援用类型数据保留在堆内存中,援用数据类型的变量是一个指向堆内存中理论对象的援用,存在栈中。

根本类型赋值,零碎会为新的变量在栈内存中调配一个新值,这个很好了解。援用类型赋值,零碎会为新的变量在栈内存中调配一个值,这个值仅仅是指向同一个对象的援用,和原对象指向的都是堆内存中的同一个对象。

对于函数,解释器创立了”调用栈“来记录函数的调用过程。每调用一个函数,解释器就能够把该函数增加进调用栈,解释器会为被增加进来的函数创立一个栈帧(用来保留函数的局部变量以及执行语句)并立刻执行。如果正在执行的函数还调用了其余函数,新函数会持续被增加进入调用栈。函数执行实现,对应的栈帧立刻被销毁。

两种查看调用栈的办法

  1. 应用 console.trace()”) 向 Web 控制台输入一个堆栈跟踪.
  2. 浏览器开发者工具进行断点调试

栈尽管很轻量,在应用时创立,应用完结后销毁,然而不是能够有限增长的,被调配的调用栈空间被占满时,就会引起”栈溢出“的谬误。

(function foo() {foo()
})()

为什么根本数据类型存储在栈中,援用数据类型存储在堆中?

JavaScript 引擎须要用栈来维护程序执行期间的上下文的状态,如果栈空间大了的话,所有数据都寄存在栈空间外面,会影响到上下文切换的效率,进而影响整个程序的执行效率。

堆空间存储的数据比较复杂,大抵能够划分为上面 5 个区域:代码区(Code Space)、Map 区(Map Space)、大对象区(Large Object Space)、新生代(New Space)、老生代(Old Space)。本篇文章次要探讨新生代和老生代的内存回收算法。

新生代内存是长期调配的内存,存活时间段,老生代内存是常驻内存,存活工夫长。

新生代内存回收

新生代中用 Scavenge 算法 来解决。所谓 Scavenge 算法,是把新生代空间对半划分为两个区域,一半是对象区域(from),一半是闲暇区域 (to)。

新的对象会首先被调配到 from 空间,当进行垃圾回收的时候,会先将 from 空间中的 存活的对象复制到 to 空间进行保留,对未存活的对象的空间进行回收。
复制实现后,from 空间和 to 空间进行调换,to 空间会变成新的 from 空间,原来的 from 空间则变成 to 空间。这种算法称之为”Scavenge“。

新生代内存回收频率很高,速度也很快,然而空间利用率很低,因为有一半的内存空间处于 ” 闲置 ” 状态。

老生代内存回收

新生代中屡次进行回收依然存活的对象会被转移到空间较大的老生代内存中,这种景象称为 降职。以下两种状况

  1. 在垃圾回收过程中,发现某个对象之前被清理过,那么将会降职到老生代的内存空间中
  2. 在 from 空间和 to 空间进行反转的过程中,如果 to 空间中的使用量曾经超过了 25%,那么就讲 from 中的对象间接降职到老生代内存空间中。

因为老生代空间较大,如果依然用 Scavenge 算法来频繁复制对象,那么性能开销就太大了。

标记 - 革除(Mark-Sweep)

老生代采纳的是”标记革除“来回收未存活的对象。

分为标记和革除两个阶段。标记阶段会遍历堆中所有的对象,并对存活的对象进行标记,革除阶段则是对未标记的对象进行革除。

标记 - 整顿(Mark-Compact)

标记革除不会对内存一分为二,所以不会节约空间。然而通过标记革除之后的内存空间会生产很多不间断的碎片空间,这种不间断的碎片空间中,在遇到较大的对象时可能会因为空间有余而导致无奈存储。
为了解决内存碎片的问题,须要应用另外一种算法 – 标记 - 整顿(Mark-Compact)。标记整顿看待未存活对象不是立刻回收,而是将存活对象挪动到一边,而后间接清掉端边界以外的内存。

增量标记

为了避免出现 JavaScript 应用程序与垃圾回收器看到的不统一的状况,进行垃圾回收的时候,都须要将正在运行的程序停下来,期待垃圾回收执行实现之后再回复程序的执行,这种景象称为“全进展”。如果须要回收的数据过多,那么全进展的时候就会比拟长,会影响其余程序的失常执行。

为了防止垃圾回收工夫过长影响其余程序的执行,V8 将标记过程分成一个个小的子标记过程,同时让垃圾回收和 JavaScript 应用逻辑代码交替执行,直到标记阶段实现。咱们称这个过程为 增量标记 算法。

艰深了解,就是把垃圾回收这个大的工作分成一个个小工作,穿插在 JavaScript 工作两头执行,这个过程其实跟 React Fiber 的设计思路相似。

参考

  • V8 引擎垃圾内存回收原理解析
  • JavaScript 中 V8 引擎内存问题
  • 浅谈 V8 引擎中的垃圾回收机制
  • 《深入浅出 Node.js》

正文完
 0