V8引擎——内存管理

1,386 阅读6分钟

1.什么是v8引擎

  • v8引擎是谷歌开源的高性能的javaScript引擎,用C++编写。它被设计用来提高网页浏览器内部JavaScript执行的性能,但同时又不局限使用在浏览器中,可以独立运行,或者可以嵌入C++的应用程序中。
  • v8引擎实现了ECMA-262中规定的ECMAScript
  • v8高性能的特性:JIT编译、垃圾回收、内联缓存、隐藏类

2.内存如何存储数据

javaScript中包含8种数据类型:Number 、 Boolean 、 Null 、 String 、 Symbol 、 Undefined 、 BigInt 、 Object ,其中object为引用数据类型,其余七种则是基本数据类型,这也就造成了它们存储的差异性。

javaScript是一门动态、弱类型的语言,动态指的是在运行过程中才去确认其变量的数据类型,弱类型语言指该语言支持隐式类型转换,如四则运算、if语句

运行js代码的时候从内容空间上分为堆空间和栈空间,这里栈空间就是创建执行上下文的调用栈。

function foo() {
    let a = 1;
    let b = 'test';
    let c = {
        num: 3;
    }
    let d = c
}
foo()

当执行这段函数时,先编译,创建执行上下文,然后逐行执行赋值代码,基本数据类型的数据就存放在在了调用栈中

然后执行到引用数据类型的赋值时,将该对象分配到堆空间中,分配后会有一个对应的内存地址,将这个内存地址赋值给变量,并存到栈空间中,这样就完成了基本数据类型和引用数据类型的存储

之所以要将基本数据类型存到栈空间中,而引用类型存到堆空间中,是因为在执行完foo函数后,切换当前执行的上下文过程中如果存放的数据体积较大,势必会影响整个程序的执行效率,所以栈空间的体积较小,堆空间中存放数据较多,体积大。

3.V8的垃圾回收

垃圾数据:当存放到内存的数据不再被使用的时候,这些数据就成为了垃圾数据,如果这些垃圾数据不被清理的话,会一直占用内存,导致内存越用越多,直至发生内存溢出,影响程序运行。

垃圾回收分为手动回收和自动回收两种策略。手动回收就是内存分配和回收都是由代码控制,如C、C++,使用自动回收则是由垃圾回收器来自动释放内存空间,如JavaScript、Java、Python。

回收栈空间中的垃圾数据

function foo(){
    let a = 1;
    let b = 3:
    function bar(){
        let c = 3;
        let d = {
            name: 'job'
        }
    }
    bar()
}
foo()

执行这段代码后,会依次向调用栈中推入foo和bar这两个执行上下文,程序执行到哪个函数时,就会有一个记录当前执行状态的指针(称为(ESP)指向该函数的执行上下文,当bar函数执行完毕后,ESP则下移指向foo函数的执行上下文,此时之前存放bar函数执行上下文的内存就无效了,如果后续有新的函数推进来,则会覆盖掉bar函数的之前使用的内存,因此javaScript引擎是通过移动ESP来清除栈中垃圾数据

回收堆空间中的垃圾数据

堆中垃圾数据是通过javaScript引擎垃圾回收器来回收,V8把堆分成了新生代和老生代两个区域,新生代存放生存时间短的对象,老生代存放生存时间长的对象

新生代 老生代
存放生存时间短的对象 存放生存时间长的对象
容量小,只支持1~8M容量 容量大
副垃圾回收器实现回收 主垃圾回收器实现回收

不管是副垃圾回收器还是主垃圾回收器都遵循这相同的执行流程

  • 标记空间中活动对象和非活动对象,活动对象就是还在使用的对象,非活动对象就只要清除的对象
  • 回收非活动对象占用的内存
  • 做内存整理,因为回收对象后,内存中会出现大量不连续的空间,这样的空间称为内存碎片,当需要分配大容量空间时,就有可能出现内存溢出的情况。

副垃圾回收器

新生代中通常情况下存放的都是小体积的对象,因此空间不大,垃圾回收较为频繁,使用副垃圾回收器来回收垃圾。新生代中使用Scavenge算法来处理,将新生代划分成了对象区域和空闲区域,新加入的对象会先存放到对象区域,待对象区域中写满时,执行一次垃圾回收操作。此时副垃圾回收器先标记对象区域中的活动对象和非活动对象,然后将活动对象复制到空闲区域,并有序的排列,避免出现内存碎片。

完成复制后,清空之前对象区域里的非活动对象,然后对象区域与空闲区域角色互换,这样就完成了一次垃圾清除。

因为这种复制需耗费时间,因此新生代的空间不会设置的太大,以免影响执行效率。

如果经历了两次垃圾回收还存活的对象,就会被移动到老生代区域,这也是为节省新生代的空间,这个现象就叫对象晋升策略。

主垃圾回收器

老生代的空间较大,除了存放比较大的对象外,还有从新生代晋升的对象,所以主垃圾回收器不使用Scavenge算法进行垃圾回收,因为复制这些大的对象会花费更多的时间,导致回收的效率不高。

主垃圾回收器采用标记-清除(Mark-Sweep)算法来进行垃圾回收,首先标记阶段,会遍历调用栈,如果找到了对堆中对象的引用地址,则将这个对象标记为活动元素,否则为非活动对象,也就是垃圾数据。遍历完成后就开始垃圾的清理,这个过程就是清除非活动对象的过程。

4.查看程序内存使用情况

通过chrome开发者工具的perfomance

参考资料

极客时间-浏览器工作原理与实践 v8文档 v8之旅