javaScript中的内存管理机制

什么是内存管理

内存就是暂时存储程序以及数据的地方,比如当我们在使用wps处理文稿时,当你在键盘上敲入字符时,它就被存入内存中,当你选择存盘时,内存中的数据才会被存入硬(磁)盘。

1.前端为什么关注内存

任何一个程序的运行,都需要分配内存空间,对于一个页面来说,任何没有得到使用的内存,没有得到及时释放,都会造成内存泄漏
一个内存泄漏或许对页面没有什么影响,但是内存泄漏堆积就可能会导致内存溢出.

  • 内存泄漏会导致页面卡顿,甚至无响应。
  • 内存溢出会导致程序运行时直接报错。

      //谁会写着么傻x的代码
      var obj = {};
      for (var i = 0; i < 1000; i++){
        obj[i] = new Array(10000000);
        console.log(i);
      };
    

2. js数据类型与js内存机制

js数据类型

  • 原始数据类型:字符串(String)、数字(Number)、布尔值(Boolean)、空对象(null)、undefined、Symbol

  • 引用数据类型:对象(Object)

js内存机制

  • 栈内存、堆内存

    原始数据类型都有固定的大小,保存在栈内存中,系统自动分配存储的空间,我们可以随时对其进行操作,按值访问

    引用数据类型的值没有固定的大小,所以存放在堆内存中,js又不允许直接操作堆栈所以我们在操作引用数据类型时,实际上是在操作对象的引用,而不是实际的对象,这里的 引用 可以理解为保存在栈内存中的指向堆内存中值的地址

js的垃圾回收机制

原理是找出那些不再继续使用的内存,然后释放掉其所占用的内存,垃圾回收机制会按照一定的时间间隔周期性的执行这一操作。
js的内存管理是自动执行并且是不可见的,因为它的自动化和不可见导致它有优点也有缺点,
优点是它大幅的减少了内存管理的代码。同事也减少了因为长时间运行带来的内存泄漏问题。
缺点是不可控,js没有任何有关操作内存的api,无法干预内存管理。

js的垃圾回收策略

  • 引用计数

    跟踪并记录每个值被引用的次数,如果一个值的引用次数为0,表示这个值不再用到了,就会将这部分内存释放掉。

        var obj = { a : 123 }; // 引用次数+1
        var obj1 = { a : 123 } //引用次数+1
        var obj = {}; //引用次数-1
        var obj = null; //引用次数为0
    

    当声明了一个变量并将一个引用类型值赋值该变量时,则这个值的引用次数就是1.如果同一个值又被赋给另外一个变量,则该值得引用次数加1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。

    严重问题: 循环引用

        function fn () {
          const objectA = { a : 123 };
          const objectB = { b : 10 };
          objectA.c = objectA;
          objectB.b = objectB;
          };
    

    objectA和objectB这两个对象通过各自的属性相互引用,引用次数都为2,永远不会为0,永远得不到回收。如果这个方法多调用几次。会导致大量的内存得不到释放。

  • 标记清除

    javaScript中重要的垃圾回收机制。

    标记清除指的是当变量进入环境时,这个变量北京标记为‘进入环境’,当b变量离开环境时标记为‘离开环境’ 最后清除标记为‘离开环境’的变量并回收他们的内存空间

    上面说的环境就是执行环境;

    执行环境分为两种

    • 全局执行环境

      最外围的执行环境,宿主环境不同,表示全局环境的对象也不同。浏览器中为window,node中为global。

      全局环境中的属性和方法都当作是window或global的属性和方法创建的。

    • 环境栈

      每个函数都有自己的执行环境,当执行流进入该函数中时,函数环境就会被推入一个栈环境中,在函数执行完成后,栈会将该环境弹出,把控制健权返还给之前的执行环境。

          function fn () {
            var a = 1; //被标记为进入环境
            var b = 2; //被标记为进入环境
          };
          fn(); // 函数执行完成a,b被标记为离开环境,内存被回收
      

3. v8引擎

一款开源高新能的javaScript引擎,v8采用的是一种分代回收的策略,将内存分为两个生代:新生代老生代,v8针对新生代和老生代使用不同的垃圾回收算法来提升垃圾回收效率。

  • 新生代

    新生代空间中的对象为存活时间较短的对象,大多数的对象被分配在这里,这个区域很小但是垃圾回特别频繁。
    在新生代使用Scavenge算法,他将堆内存一分为二,每一部分空间空间叫做semispace,其中处于使用状态的叫(form空间),另一个处于闲置状态(to空间);

    Scavenge的工作原理:

    首先检 from 空间,将存活对象复制到 to 空间,非存活对象将会被释放。完成复制后,from 空间和 to 空间角色发生转换。新产生的对象始终从 from 空间中分配内存,to 空间则处于闲置状态。当再次进行垃圾回收时,也会执行和第一次同样的操作,如果存在以下两种情况,存活对象就会被复制到老生代空间中,这个过程称为对象晋升。

    • 存活对象经历过多次scavenge回收依然存活,认为它是生命周期较长的对象。
    • to 空间内存占用比例超过 25%。
  • 老生代

    老生代空间中的对象为存活时间长或常驻内存对象,大多数从新生代晋升的对象会被移动到这里,老生代中的内存空间和新生代中的内存空间结构不一样,为一个连续的空间。

    老生代中的垃圾回收,采用标记清除(Mark-Sweep)和标记整理(Mark-Compact) 相结合。

    • 标记清除

      在标记阶段需要遍历堆中的所有对象,并标记那些需要被释放的的对象,垃圾回收运行时进入清除阶段。在清除阶段,只清除被标记的对象,并且是相应的内存空间。由于标记清除只清除死亡对象,而死亡对象在老生代中占用的比例很小,所以效率较高。

      标记清除存在的问题是,进行一次标记清除后,内存空间往往是不连续的,会出现很多的内存碎片。如果后续需要分配一个需要内存空间较多的对象时,如果所有的内存碎片都不够用,将会使得V8无法完成这次分配,提前触发垃圾回收。

    • 标记整理

      标记整理正是为了解决标记清除所带来的内存碎片的问题。标记整理在标记清除的基础进行修改,将其的清除阶段变为紧缩阶段。在整理的过程中,将活着的对象向内存区的一段移动,移动完成后直接清理掉边界外的内存。紧缩过程涉及对象的移动,所以效率并不是太好,但是能保证不会生成内存碎片。

      V8 主要使用 标记清除,在空间不足以对新生代中晋升过来的对象进行分配时才使用标记整理。

posted on 2020-07-07 11:36  雨田Br  阅读(369)  评论(0编辑  收藏  举报

导航